1 2var _old_define = define; 3var _old_require = require; 4define = undefined; 5require = undefined; 6try{ 7 8/*! 9 * pickadate.js v3.5.6, 2015/04/20 10 * By Amsul, http://amsul.ca 11 * Hosted on http://amsul.github.io/pickadate.js 12 * Licensed under MIT 13 */ 14 15(function ( factory ) { 16 17 // AMD. 18 if ( typeof define == 'function' && define.amd ) 19 define( 'picker', ['jquery'], factory ) 20 21 // Node.js/browserify. 22 else if ( typeof exports == 'object' ) 23 module.exports = factory( require('jquery') ) 24 25 // Browser globals. 26 else this.Picker = factory( jQuery ) 27 28}(function( $ ) { 29 30var $window = $( window ) 31var $document = $( document ) 32var $html = $( document.documentElement ) 33var supportsTransitions = document.documentElement.style.transition != null 34 35 36/** 37 * The picker constructor that creates a blank picker. 38 */ 39function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) { 40 41 // If thereâs no element, return the picker constructor. 42 if ( !ELEMENT ) return PickerConstructor 43 44 45 var 46 IS_DEFAULT_THEME = false, 47 48 49 // The state of the picker. 50 STATE = { 51 id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) ) 52 }, 53 54 55 // Merge the defaults and options passed. 56 SETTINGS = COMPONENT ? $.extend( true, {}, COMPONENT.defaults, OPTIONS ) : OPTIONS || {}, 57 58 59 // Merge the default classes with the settings classes. 60 CLASSES = $.extend( {}, PickerConstructor.klasses(), SETTINGS.klass ), 61 62 63 // The element node wrapper into a jQuery object. 64 $ELEMENT = $( ELEMENT ), 65 66 67 // Pseudo picker constructor. 68 PickerInstance = function() { 69 return this.start() 70 }, 71 72 73 // The picker prototype. 74 P = PickerInstance.prototype = { 75 76 constructor: PickerInstance, 77 78 $node: $ELEMENT, 79 80 81 /** 82 * Initialize everything 83 */ 84 start: function() { 85 86 // If itâs already started, do nothing. 87 if ( STATE && STATE.start ) return P 88 89 90 // Update the picker states. 91 STATE.methods = {} 92 STATE.start = true 93 STATE.open = false 94 STATE.type = ELEMENT.type 95 96 97 // Confirm focus state, convert into text input to remove UA stylings, 98 // and set as readonly to prevent keyboard popup. 99 ELEMENT.autofocus = ELEMENT == getActiveElement() 100 ELEMENT.readOnly = !SETTINGS.editable 101 ELEMENT.id = ELEMENT.id || STATE.id 102 if ( ELEMENT.type != 'text' ) { 103 ELEMENT.type = 'text' 104 } 105 106 107 // Create a new picker component with the settings. 108 P.component = new COMPONENT(P, SETTINGS) 109 110 111 // Create the picker root and then prepare it. 112 P.$root = $( '<div class="' + CLASSES.picker + '" id="' + ELEMENT.id + '_root" />' ) 113 prepareElementRoot() 114 115 116 // Create the picker holder and then prepare it. 117 P.$holder = $( createWrappedComponent() ).appendTo( P.$root ) 118 prepareElementHolder() 119 120 121 // If thereâs a format for the hidden input element, create the element. 122 if ( SETTINGS.formatSubmit ) { 123 prepareElementHidden() 124 } 125 126 127 // Prepare the input element. 128 prepareElement() 129 130 131 // Insert the hidden input as specified in the settings. 132 if ( SETTINGS.containerHidden ) $( SETTINGS.containerHidden ).append( P._hidden ) 133 else $ELEMENT.after( P._hidden ) 134 135 136 // Insert the root as specified in the settings. 137 if ( SETTINGS.container ) $( SETTINGS.container ).append( P.$root ) 138 else $ELEMENT.after( P.$root ) 139 140 141 // Bind the default component and settings events. 142 P.on({ 143 start: P.component.onStart, 144 render: P.component.onRender, 145 stop: P.component.onStop, 146 open: P.component.onOpen, 147 close: P.component.onClose, 148 set: P.component.onSet 149 }).on({ 150 start: SETTINGS.onStart, 151 render: SETTINGS.onRender, 152 stop: SETTINGS.onStop, 153 open: SETTINGS.onOpen, 154 close: SETTINGS.onClose, 155 set: SETTINGS.onSet 156 }) 157 158 159 // Once weâre all set, check the theme in use. 160 IS_DEFAULT_THEME = isUsingDefaultTheme( P.$holder[0] ) 161 162 163 // If the element has autofocus, open the picker. 164 if ( ELEMENT.autofocus ) { 165 P.open() 166 } 167 168 169 // Trigger queued the âstartâ and ârenderâ events. 170 return P.trigger( 'start' ).trigger( 'render' ) 171 }, //start 172 173 174 /**
175 * Render a new picker 176 */ 177 render: function( entireComponent ) { 178 179 // Insert a new component holder in the root or box. 180 if ( entireComponent ) { 181 P.$holder = $( createWrappedComponent() ) 182 prepareElementHolder() 183 P.$root.html( P.$holder ) 184 } 185 else P.$root.find( '.' + CLASSES.box ).html( P.component.nodes( STATE.open ) ) 186 187 // Trigger the queued ârenderâ events. 188 return P.trigger( 'render' ) 189 }, //render 190 191 192 /** 193 * Destroy everything 194 */ 195 stop: function() { 196 197 // If itâs already stopped, do nothing. 198 if ( !STATE.start ) return P 199 200 // Then close the picker. 201 P.close() 202 203 // Remove the hidden field. 204 if ( P._hidden ) { 205 P._hidden.parentNode.removeChild( P._hidden ) 206 } 207 208 // Remove the root. 209 P.$root.remove() 210 211 // Remove the input class, remove the stored data, and unbind 212 // the events (after a tick for IE - see `P.close`). 213 $ELEMENT.removeClass( CLASSES.input ).removeData( NAME ) 214 setTimeout( function() { 215 $ELEMENT.off( '.' + STATE.id ) 216 }, 0) 217 218 // Restore the element state 219 ELEMENT.type = STATE.type 220 ELEMENT.readOnly = false 221 222 // Trigger the queued âstopâ events. 223 P.trigger( 'stop' ) 224 225 // Reset the picker states. 226 STATE.methods = {} 227 STATE.start = false 228 229 return P 230 }, //stop 231 232 233 /** 234 * Open up the picker 235 */ 236 open: function( dontGiveFocus ) { 237 238 // If itâs already open, do nothing. 239 if ( STATE.open ) return P 240 241 // Add the âactiveâ class. 242 $ELEMENT.addClass( CLASSES.active ) 243 aria( ELEMENT, 'expanded', true ) 244 245 // * A Firefox bug, when `html` has `overflow:hidden`, results in 246 // killing transitions :(. So add the âopenedâ state on the next tick. 247 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289 248 setTimeout( function() { 249 250 // Add the âopenedâ class to the picker root. 251 P.$root.addClass( CLASSES.opened ) 252 aria( P.$root[0], 'hidden', false ) 253 254 }, 0 ) 255 256 // If we have to give focus, bind the element and doc events. 257 if ( dontGiveFocus !== false ) { 258 259 // Set it as open. 260 STATE.open = true 261 262 // Prevent the page from scrolling. 263 if ( IS_DEFAULT_THEME ) { 264 $html. 265 css( 'overflow', 'hidden' ). 266 css( 'padding-right', '+=' + getScrollbarWidth() ) 267 } 268 269 // Pass focus to the root elementâs jQuery object. 270 focusPickerOnceOpened() 271 272 // Bind the document events. 273 $document.on( 'click.' + STATE.id + ' focusin.' + STATE.id, function( event ) { 274 275 var target = event.target 276 277 // If the target of the event is not the element, close the picker picker. 278 // * Donât worry about clicks or focusins on the root because those donât bubble up. 279 // Also, for Firefox, a click on an `option` element bubbles up directly 280 // to the doc. So make sure the target wasn't the doc. 281 // * In Firefox stopPropagation() doesnât prevent right-click events from bubbling, 282 // which causes the picker to unexpectedly close when right-clicking it. So make 283 // sure the event wasnât a right-click. 284 if ( target != ELEMENT && target != document && event.which != 3 ) { 285 286 // If the target was the holder that c
286overs the screen, 287 // keep the element focused to maintain tabindex. 288 P.close( target === P.$holder[0] ) 289 } 290 291 }).on( 'keydown.' + STATE.id, function( event ) { 292 293 var 294 // Get the keycode. 295 keycode = event.keyCode, 296 297 // Translate that to a selection change. 298 keycodeToMove = P.component.key[ keycode ], 299 300 // Grab the target. 301 target = event.target 302 303 304 // On escape, close the picker and give focus. 305 if ( keycode == 27 ) { 306 P.close( true ) 307 } 308 309 310 // Check if there is a key movement or âenterâ keypress on the element. 311 else if ( target == P.$holder[0] && ( keycodeToMove || keycode == 13 ) ) { 312 313 // Prevent the default action to stop page movement. 314 event.preventDefault() 315 316 // Trigger the key movement action. 317 if ( keycodeToMove ) { 318 PickerConstructor._.trigger( P.component.key.go, P, [ PickerConstructor._.trigger( keycodeToMove ) ] ) 319 } 320 321 // On âenterâ, if the highlighted item isnât disabled, set the value and close. 322 else if ( !P.$root.find( '.' + CLASSES.highlighted ).hasClass( CLASSES.disabled ) ) { 323 P.set( 'select', P.component.item.highlight ) 324 if ( SETTINGS.closeOnSelect ) { 325 P.close( true ) 326 } 327 } 328 } 329 330 331 // If the target is within the root and âenterâ is pressed, 332 // prevent the default action and trigger a click on the target instead. 333 else if ( $.contains( P.$root[0], target ) && keycode == 13 ) { 334 event.preventDefault() 335 target.click() 336 } 337 }) 338 } 339 340 // Trigger the queued âopenâ events. 341 return P.trigger( 'open' ) 342 }, //open 343 344 345 /** 346 * Close the picker 347 */ 348 close: function( giveFocus ) { 349 350 // If we need to give focus, do it before changing states. 351 if ( giveFocus ) { 352 if ( SETTINGS.editable ) { 353 ELEMENT.focus() 354 } 355 else { 356 // ....ah yes! It wouldâve been incomplete without a crazy workaround for IE :| 357 // The focus is triggered *after* the close has completed - causing it 358 // to open again. So unbind and rebind the event at the next tick. 359 P.$holder.off( 'focus.toOpen' ).focus() 360 setTimeout( function() { 361 P.$holder.on( 'focus.toOpen', handleFocusToOpenEvent ) 362 }, 0 ) 363 } 364 } 365 366 // Remove the âactiveâ class. 367 $ELEMENT.removeClass( CLASSES.active ) 368 aria( ELEMENT, 'expanded', false ) 369 370 // * A Firefox bug, when `html` has `overflow:hidden`, results in 371 // killing transitions :(. So remove the âopenedâ state on the next tick. 372 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289 373 setTimeout( function() { 374 375 // Remove the âopenedâ and âfocusedâ class from the picker root. 376 P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused ) 377 aria( P.$root[0], 'hidden', true ) 378 379 }, 0 ) 380 381 // If itâs already closed, do nothing more. 382 if ( !STATE.open ) return P 383 384 // Set it as closed. 385 STATE.open = false 386 387 // Allow the page to scroll. 388 if ( IS_DEFAULT_THEME ) { 389 $html. 390 css( 'overflow', '' ). 391 css( 'padding-right', '-=' + getScrollbarWidth() ) 392 } 393 394 // Unbind the document events. 395 $document.off( '.' + STATE.id ) 396 397 // Trigger the queued âcloseâ events. 398 return P.trigger( 'close' ) 399 }, //close 400 401 402 /** 403 * Clear the values 404 */ 405 clear: function( options ) { 406 return P.set( 'clear', null, options ) 407 }, //clear 408 409 410 /** 411 * Set something 412 */ 413 set: function( thing, value, options ) { 414 415 var thingItem, thingValue, 416 thingIsObject = $.isPlainObject( thing ), 417 thingObject = thingIsObject ? thing : {} 418 419 // Make sure we have usable options. 420 options = thingIsObject && $.isPlainObject( value ) ? value : options || {} 421 422 if ( thing ) { 423 424 // If the thing isnât an object, make it one. 425 if ( !thingIsObject ) { 426 thingObject[ thing ] = value 427 } 428 429 // Go through the things of items to set. 430 for ( thingItem in thingObject ) { 431 432 // Grab the value of the thing. 433 thingValue = thingObject[ thingItem ] 434 435 // First, if the item exists and thereâs a value, set it. 436 if ( thingItem in P.component.item ) { 437 if ( thingValue === undefined ) thingValue = null 438 P.component.set( thingItem, thingValue, options ) 439 } 440 441 // Then, check to update the element value and broadcast a change. 442 if ( thingItem == 'select' || thingItem == 'clear' ) { 443 $ELEMENT. 444 val( thingItem == 'clear' ? '' : P.get( thingItem, SETTINGS.format ) ). 445 trigger( 'change' ) 446 } 447 } 448 449 // Render a new picker. 450 P.render() 451 } 452 453 // When the method isnât muted, trigger queued âsetâ events and pass the `thingObject`. 454 return options.muted ? P : P.trigger( 'set', thingObject ) 455 }, //set 456 457 458 /** 459 * Get something 460 */ 461 get: function( thing, format ) { 462 463 // Make sure thereâs something to get. 464 thing = thing || 'value' 465 466 // If a picker state exists, return that. 467 if ( STATE[ thing ] != null ) { 468 return STATE[ thing ] 469 } 470 471 // Return the submission value, if that. 472 if ( thing == 'valueSubmit' ) { 473 if ( P._hidden ) { 474 return P._hidden.value 475 } 476 thing = 'value' 477 } 478 479 // Return the value, if that. 480 if ( thing == 'value' ) { 481 return ELEMENT.value 482 } 483 484 // Check if a component item exists, return that. 485 if ( thing in P.component.item ) { 486 if ( typeof format == 'string' ) { 487 var thingValue = P.component.get( thing ) 488 return thingValue ? 489 PickerConstructor._.trigger( 490 P.component.formats.toString, 491 P.component, 492 [ format, thingValue ] 493 ) : '' 494 } 495 return P.component.get( thing ) 496 } 497 }, //get 498 499 500 501 /** 502 * Bind events on the things. 503 */ 504 on: function( thing, method, internal ) { 505 506 var thingName, thingMethod, 507 thingIsObject = $.isPlainObject( thing ), 508 thingObject = thingIsObject ? thing : {} 509 510 if ( thing ) { 511 512 // If the thing isnât an object, make it one. 513 if ( !thingIsObject ) { 514 thingObject[ thing ] = method 515 } 516 517 // Go through the things to bind to. 518 for ( thingName in thingObject ) { 519 520 // Grab the method of the thing. 521 thingMethod = thingObject[ thingName ] 522 523 // If it was an internal binding, prefix it. 524 if ( internal ) { 525 thingName = '_' + thingName 526 } 527 528 // Make sure the thing methods collection exists. 529 STATE.methods[ thingName ] = STATE.methods[ thingName ] || [] 530 531 // Add the method to the relative method collection. 532 STATE.methods[ thingName ].push( thingMethod ) 533 } 534 } 535 536 return P 537 }, //on 538 539 540 541 /** 542 * Unbind events on the things. 543 */ 544 off: function() { 545 var i, thingName, 546 names = arguments; 547 for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) { 548 thingName = names[i] 549 if ( thingName in STATE.methods ) { 550 delete STATE.methods[thingName] 551 } 552 } 553 return P 554 }, 555 556 557 /** 558 * Fire off method events. 559 */ 560 trigger: function( name, data ) { 561 var _trigger = function( name ) { 562 var methodList = STATE.methods[ name ] 563 if ( methodList ) { 564 methodList.map( function( method ) { 565 PickerConstructor._.trigger( method, P, [ data ] ) 566 }) 567 } 568 } 569 _trigger( '_' + name ) 570 _trigger( name ) 571 return P 572 }
572 //trigger 573 } //PickerInstance.prototype 574 575 576 /** 577 * Wrap the picker holder components together. 578 */ 579 function createWrappedComponent() { 580 581 // Create a picker wrapper holder 582 return PickerConstructor._.node( 'div', 583 584 // Create a picker wrapper node 585 PickerConstructor._.node( 'div', 586 587 // Create a picker frame 588 PickerConstructor._.node( 'div', 589 590 // Create a picker box node 591 PickerConstructor._.node( 'div', 592 593 // Create the components nodes. 594 P.component.nodes( STATE.open ), 595 596 // The picker box class 597 CLASSES.box 598 ), 599 600 // Picker wrap class 601 CLASSES.wrap 602 ), 603 604 // Picker frame class 605 CLASSES.frame 606 ), 607 608 // Picker holder class 609 CLASSES.holder, 610 611 'tabindex="-1"' 612 ) //endreturn 613 } //createWrappedComponent 614 615 616 617 /** 618 * Prepare the input element with all bindings. 619 */ 620 function prepareElement() { 621 622 $ELEMENT. 623 624 // Store the picker data by component name. 625 data(NAME, P). 626 627 // Add the âinputâ class name. 628 addClass(CLASSES.input). 629 630 // If thereâs a `data-value`, update the value of the element. 631 val( $ELEMENT.data('value') ? 632 P.get('select', SETTINGS.format) : 633 ELEMENT.value 634 ) 635 636 637 // Only bind keydown events if the element isnât editable. 638 if ( !SETTINGS.editable ) { 639 640 $ELEMENT. 641 642 // On focus/click, open the picker. 643 on( 'focus.' + STATE.id + ' click.' + STATE.id, function(event) { 644 event.preventDefault() 645 P.open() 646 }). 647 648 // Handle keyboard event based on the picker being opened or not. 649 on( 'keydown.' + STATE.id, handleKeydownEvent ) 650 } 651 652 653 // Update the aria attributes. 654 aria(ELEMENT, { 655 haspopup: true, 656 expanded: false, 657 readonly: false, 658 owns: ELEMENT.id + '_root' 659 }) 660 } 661 662 663 /** 664 * Prepare the root picker element with all bindings. 665 */ 666 function prepareElementRoot() { 667 aria( P.$root[0], 'hidden', true ) 668 } 669 670 671 /** 672 * Prepare the holder picker element with all bindings. 673 */ 674 function prepareElementHolder() { 675 676 P.$holder. 677 678 on({ 679 680 // For iOS8. 681 keydown: handleKeydownEvent, 682 683 'focus.toOpen': handleFocusToOpenEvent, 684 685 blur: function() { 686 // Remove the âtargetâ class. 687 $ELEMENT.removeClass( CLASSES.target ) 688 }, 689 690 // When something within the holder is focused, stop from bubbling 691 // to the doc and remove the âfocusedâ state from the root. 692 focusin: function( event ) { 693 P.$root.removeClass( CLASSES.focused ) 694 event.stopPropagation() 695 }, 696 697 // When something within the holder is clicked, stop it 698 // from bubbling to the doc. 699 'mousedown click': function( event ) { 700 701 var target = event.target 702 703 // Make sure the target isnât the root holder so it can bubble up. 704 if ( target != P.$holder[0] ) { 705 706 event.stopPropagation() 707 708 // * For mousedown events, cancel the default action in order to 709 // prevent cases where focus is shifted onto external elements 710 // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120). 711 // Also, for Firefox, donât prevent action on the `option` element. 712 if ( event.type == 'mousedown' && !$( target ).is( 'input, select, textarea, button, option' )) { 713 714 event.preventDefault() 715 716 // Re-focus onto the holder so that users can click away 717 // from elements focused within the picker. 718 P.$holder[0].focus() 719 } 720 } 721 } 722 723 }). 724 725 // If thereâs a click on an actionable element, carry out the actions.
726 on( 'click', '[data-pick], [data-nav], [data-clear], [data-close]', function() { 727 728 var $target = $( this ), 729 targetData = $target.data(), 730 targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ), 731 732 // * For IE, non-focusable elements can be active elements as well 733 // (http://stackoverflow.com/a/2684561). 734 activeElement = getActiveElement() 735 activeElement = activeElement && ( activeElement.type || activeElement.href ) 736 737 // If itâs disabled or nothing inside is actively focused, re-focus the element. 738 if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) { 739 P.$holder[0].focus() 740 } 741 742 // If something is superficially changed, update the `highlight` based on the `nav`. 743 if ( !targetDisabled && targetData.nav ) { 744 P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } ) 745 } 746 747 // If something is picked, set `select` then close with focus. 748 else if ( !targetDisabled && 'pick' in targetData ) { 749 P.set( 'select', targetData.pick ) 750 if ( SETTINGS.closeOnSelect ) { 751 P.close( true ) 752 } 753 } 754 755 // If a âclearâ button is pressed, empty the values and close with focus. 756 else if ( targetData.clear ) { 757 P.clear() 758 if ( SETTINGS.closeOnClear ) { 759 P.close( true ) 760 } 761 } 762 763 else if ( targetData.close ) { 764 P.close( true ) 765 } 766 767 }) //P.$holder 768 769 } 770 771 772 /** 773 * Prepare the hidden input element along with all bindings. 774 */ 775 function prepareElementHidden() { 776 777 var name 778 779 if ( SETTINGS.hiddenName === true ) { 780 name = ELEMENT.name 781 ELEMENT.name = '' 782 } 783 else { 784 name = [ 785 typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '', 786 typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit' 787 ] 788 name = name[0] + ELEMENT.name + name[1] 789 } 790 791 P._hidden = $( 792 '<input ' + 793 'type=hidden ' + 794 795 // Create the name using the original inputâs with a prefix and suffix. 796 'name="' + name + '"' + 797 798 // If the element has a value, set the hidden value as well. 799 ( 800 $ELEMENT.data('value') || ELEMENT.value ? 801 ' value="' + P.get('select', SETTINGS.formatSubmit) + '"' : 802 '' 803 ) + 804 '>' 805 )[0] 806 807 $ELEMENT. 808 809 // If the value changes, update the hidden input with the correct format. 810 on('change.' + STATE.id, function() { 811 P._hidden.value = ELEMENT.value ? 812 P.get('select', SETTINGS.formatSubmit) : 813 '' 814 }) 815 } 816 817 818 // Wait for transitions to end before focusing the holder. Otherwise, while 819 // using the `container` option, the view jumps to the container. 820 function focusPickerOnceOpened() { 821 822 if (IS_DEFAULT_THEME && supportsTransitions) { 823 P.$holder.find('.' + CLASSES.frame).one('transitionend', function() { 824 P.$holder[0].focus() 825 }) 826 } 827 else { 828 P.$holder[0].focus() 829 } 830 } 831 832 833 function handleFocusToOpenEvent(event) { 834 835 // Stop the event from propagating to the doc. 836 event.stopPropagation() 837 838 // Add the âtargetâ class. 839 $ELEMENT.addClass( CLASSES.target ) 840 841 // Add the âfocusedâ class to the root. 842 P.$root.addClass( CLASSES.focused ) 843 844 // And then finally open the picker. 845 P.open() 846 } 847 848 849 // For iOS8. 850 function handleKeydownEvent( event ) { 851 852 var keycode = event.keyCode, 853 854 // Check if one of the delete keys was pressed. 855 isKeycodeDelete = /^(8|46)$/.test(keycode) 856 857 // For some reason IE clears the input value on âescapeâ. 858 if ( keycode == 27 ) { 859 P.close( true ) 860 return false 861 } 862 863 // Check if `space` or `delete` was pressed or the picker is closed with a key movement. 864 if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) { 865
866 // Prevent it from moving the page and bubbling to doc. 867 event.preventDefault() 868 event.stopPropagation() 869 870 // If `delete` was pressed, clear the values and close the picker. 871 // Otherwise open the picker. 872 if ( isKeycodeDelete ) { P.clear().close() } 873 else { P.open() } 874 } 875 } 876 877 878 // Return a new picker instance. 879 return new PickerInstance() 880} //PickerConstructor 881 882 883 884/** 885 * The default classes and prefix to use for the HTML classes. 886 */ 887PickerConstructor.klasses = function( prefix ) { 888 prefix = prefix || 'picker' 889 return { 890 891 picker: prefix, 892 opened: prefix + '--opened', 893 focused: prefix + '--focused', 894 895 input: prefix + '__input', 896 active: prefix + '__input--active', 897 target: prefix + '__input--target', 898 899 holder: prefix + '__holder', 900 901 frame: prefix + '__frame', 902 wrap: prefix + '__wrap', 903 904 box: prefix + '__box' 905 } 906} //PickerConstructor.klasses 907 908 909 910/** 911 * Check if the default theme is being used. 912 */ 913function isUsingDefaultTheme( element ) { 914 915 var theme, 916 prop = 'position' 917 918 // For IE. 919 if ( element.currentStyle ) { 920 theme = element.currentStyle[prop] 921 } 922 923 // For normal browsers. 924 else if ( window.getComputedStyle ) { 925 theme = getComputedStyle( element )[prop] 926 } 927 928 return theme == 'fixed' 929} 930 931 932 933/** 934 * Get the width of the browserâs scrollbar. 935 * Taken from: https://github.com/VodkaBears/Remodal/blob/master/src/jquery.remodal.js 936 */ 937function getScrollbarWidth() { 938 939 if ( $html.height() <= $window.height() ) { 940 return 0 941 } 942 943 var $outer = $( '<div style="visibility:hidden;width:100px" />' ). 944 appendTo( 'body' ) 945 946 // Get the width without scrollbars. 947 var widthWithoutScroll = $outer[0].offsetWidth 948 949 // Force adding scrollbars. 950 $outer.css( 'overflow', 'scroll' ) 951 952 // Add the inner div. 953 var $inner = $( '<div style="width:100%" />' ).appendTo( $outer ) 954 955 // Get the width with scrollbars. 956 var widthWithScroll = $inner[0].offsetWidth 957 958 // Remove the divs. 959 $outer.remove() 960 961 // Return the difference between the widths. 962 return widthWithoutScroll - widthWithScroll 963} 964 965 966 967/** 968 * PickerConstructor helper methods. 969 */ 970PickerConstructor._ = { 971 972 /** 973 * Create a group of nodes. Expects: 974 * ` 975 { 976 min: {Integer}, 977 max: {Integer}, 978 i: {Integer}, 979 node: {String}, 980 item: {Function} 981 } 982 * ` 983 */ 984 group: function( groupObject ) { 985 986 var 987 // Scope for the looped object 988 loopObjectScope, 989 990 // Create the nodes list 991 nodesList = '', 992 993 // The counter starts from the `min` 994 counter = PickerConstructor._.trigger( groupObject.min, groupObject ) 995 996 997 // Loop from the `min` to `max`, incrementing by `i` 998 for ( ; counter <= PickerConstructor._.trigger( groupObject.max, groupObject, [ counter ] ); counter += groupObject.i ) { 999 1000 // Trigger the `item` function within scope of the object 1001 loopObjectScope = PickerConstructor._.trigger( groupObject.item, groupObject, [ counter ] ) 1002 1003 // Splice the subgroup and create nodes out of the sub nodes 1004 nodesList += PickerConstructor._.node( 1005 groupObject.node, 1006 loopObjectScope[ 0 ], // the node 1007 loopObjectScope[ 1 ], // the classes 1008 loopObjectScope[ 2 ] // the attributes 1009 ) 1010 } 1011 1012 // Return the list of nodes 1013 return nodesList 1014 }, //group 1015 1016 1017 /** 1018 * Create a dom node string 1019 */ 1020 node: function( wrapper, item, klass, attribute ) { 1021 1022 // If the item is false-y, just return an empty string 1023 if ( !item ) return '' 1024 1025 // If the item is an array, do a join 1026 item = $.isArray( item ) ? item.join( '' ) : item 1027 1028 // Check for the class 1029 klass = klass ? ' class="' + klass + '"' : '' 1030 1031 // Check for any attributes 1032 attribute = attribute ? ' ' + attribute : '' 1033 1034 // Return the wrapped item 1035 return '<' + wrapper + klass + attribute + '>' + item + '</' + wrapper + '>' 1036 }, //node 1037 1038 1039 /** 1040 * Lead numbers below 10 with a zero. 1041 */ 1042 lead: function( number ) { 1043 return ( number < 10 ? '0': '' ) + number 1044 }, 1045 1046 1047 /** 1048 * Trigger a function otherwise return the value. 1049 */ 1050 trigger: function( callback, scope, args ) { 1051 return typeof callback == 'function' ? callback.apply( scope, args || [] ) : callback 1052 }, 1053 1054 1055 /** 1056 * If the second character is a digit, length is 2 otherwise 1. 1057 */ 1058 digits: function( string ) { 1059 return ( /\d/ ).test( string[ 1 ] ) ? 2 : 1 1060 }, 1061 1062 1063 /** 1064 * Tell if something is a date object. 1065 */ 1066 isDate: function( value ) { 1067 return {}.toString.call( value ).indexOf( 'Date' ) > -1 && this.isInteger( value.getDate() ) 1068 }, 1069 1070 1071 /** 1072 * Tell if something is an integer. 1073 */ 1074 isInteger: function( value ) { 1075 return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0 1076 }, 1077 1078 1079 /** 1080 * Create ARIA attribute strings. 1081 */ 1082 ariaAttr: ariaAttr 1083} //PickerConstructor._ 1084 1085 1086 1087/** 1088 * Extend the picker with a component and defaults. 1089 */ 1090PickerConstructor.extend = function( name, Component ) { 1091 1092 // Extend jQuery. 1093 $.fn[ name ] = function( options, action ) { 1094 1095 // Grab the component data. 1096 var componentData = this.data( name ) 1097 1098 // If the picker is requested, return the data object. 1099 if ( options == 'picker' ) { 1100 return componentData 1101 } 1102 1103 // If the component data exists and `options` is a string, carry out the action. 1104 if ( componentData && typeof options == 'string' ) { 1105 return PickerConstructor._.trigger( componentData[ options ], componentData, [ action ] ) 1106 } 1107 1108 // Otherwise go through each matched element and if the component 1109 // doesnât exist, create a new picker using `this` element 1110 // and merging the defaults and options with a deep copy. 1111 return this.each( function() { 1112 var $this = $( this ) 1113 if ( !$this.data( name ) ) { 1114 new PickerConstructor( this, name, Component, options ) 1115 } 1116 }) 1117 } 1118 1119 // Set the defaults. 1120 $.fn[ name ].defaults = Component.defaults 1121} //PickerConstructor.extend 1122 1123 1124 1125function aria(element, attribute, value) { 1126 if ( $.isPlainObject(attribute) ) { 1127 for ( var key in attribute ) { 1128 ariaSet(element, key, attribute[key]) 1129 } 1130 } 1131 else { 1132 ariaSet(element, attribute, value) 1133 } 1134} 1135function ariaSet(element, attribute, value) { 1136 element.setAttribute( 1137 (attribute == 'role' ? '' : 'aria-') + attribute, 1138 value 1139 ) 1140} 1141function ariaAttr(attribute, data) { 1142 if ( !$.isPlainObject(attribute) ) { 1143 attribute = { attribute: data } 1144 } 1145 data = '' 1146 for ( var key in attribute ) { 1147 var attr = (key == 'role' ? '' : 'aria-') + key, 1148 attrVal = attribute[key] 1149 data += attrVal == null ? '' : attr + '="' + attribute[key] + '"' 1150 } 1151 return data 1152} 1153 1154// IE8 bug throws an error for activeElements within iframes. 1155function getActiveElement() { 1156 try { 1157 return document.activeElement 1158 } catch ( err ) { } 1159} 1160 1161 1162 1163// Expose the picker constructor. 1164return PickerConstructor 1165 1166 1167})); 1168 1169 1170 1171/*! 1172 * Date picker for pickadate.js v3.5.6 1173 * http://amsul.github.io/pickadate.js/date.htm 1174 */ 1175 1176(function ( factory ) { 1177 1178 // AMD. 1179 if ( typeof define == 'function' && define.amd ) 1180 define( ['picker', 'jquery'], factory ) 1181 1182 // Node.js/browserify. 1183 else if ( typeof exports == 'object' ) 1184 module.exports = factory( require('./picker.js'), require('jquery') ) 1185 1186 // Browser globals. 1187 else factory( Picker, jQuery ) 1188 1189}(function( Picker, $ ) { 1190 1191 1192/** 1193 * Globals and constants 1194 */ 1195var DAYS_IN_WEEK = 7, 1196 WEEKS_IN_CALENDAR = 6, 1197 _ = Picker._ 1198 1199 1200 1201/** 1202 * The date picker constructor 1203 */ 1204function DatePicker( picker, settings ) { 1205 1206 var calendar = this, 1207 element = picker.$node[ 0 ], 1208 elementValue = element.value, 1209 elementDataValue = picker.$node.data( 'value' ), 1210 valueString = elementDataValue || elementValue, 1211 formatString = elementDataValue ? settings.formatSubmit : settings.format, 1212 isRTL = function() { 1213 1214 return element.currentStyle ? 1215 1216 // For IE. 1217 element.currentStyle.direction == 'rtl' : 1218 1219 // For normal browsers. 1220 getComputedStyle( picker.$root[0] ).direction == 'rtl' 1221 } 1222 1223 calendar.settings = settings 1224 calendar.$node = picker.$node 1225 1226 // The queue of methods that will be used to build item objects. 1227 calendar.queue = { 1228 min: 'measure create', 1229 max: 'measure create', 1230 now: 'now create', 1231 select: 'parse create validate', 1232 highlight: 'parse navigate create validate', 1233 view: 'parse create validate viewset', 1234 disable: 'deactivate', 1235 enable: 'activate' 1236 } 1237 1238 // The component's item object. 1239 calendar.item = {} 1240 1241 calendar.item.clear = null 1242 calendar.item.disable = ( settings.disable || [] ).slice( 0 ) 1243 calendar.item.enable = -(function( collectionDisabled ) { 1244 return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1 1245 })( calendar.item.disable ) 1246 1247 calendar. 1248 set( 'min', settings.min ). 1249 set( 'max', settings.max ). 1250 set( 'now' ) 1251 1252 // When thereâs a value, set the `select`, which in turn 1253 // also sets the `highlight` and `view`. 1254 if ( valueString ) { 1255 calendar.set( 'select', valueString, { 1256 format: formatString, 1257 defaultValue: true 1258 }) 1259 } 1260 1261 // If thereâs no value, default to highlighting âtodayâ. 1262 else { 1263 calendar. 1264 set( 'select', null ). 1265 set( 'highlight', calendar.item.now ) 1266 } 1267 1268 1269 // The keycode to movement mapping. 1270 calendar.key = { 1271 40: 7, // Down 1272 38: -7, // Up 1273 39: function() { return isRTL() ? -1 : 1 }, // Right 1274 37: function() { return isRTL() ? 1 : -1 }, // Left 1275 go: function( timeChange ) { 1276 var highlightedObject = calendar.item.highlight, 1277 targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange ) 1278 calendar.set( 1279 'highlight', 1280 targetDate, 1281 { interval: timeChange } 1282 ) 1283 this.render() 1284 } 1285 } 1286 1287 1288 // Bind some picker events. 1289 picker. 1290 on( 'render', function() { 1291 picker.$root.find( '.' + settings.klass.selectMonth ).on( 'change', function() { 1292 var value = this.value 1293 if ( value ) { 1294 picker.set( 'highlight', [ picker.get( 'view' ).year, value, picker.get( 'highlight' ).date ] ) 1295 picker.$root.find( '.' + settings.klass.selectMonth ).trigger( 'focus' ) 1296 } 1297 }) 1298 picker.$root.find( '.' + settings.klass.selectYear ).on( 'change', function() { 1299 var value = this.value 1300 if ( value ) { 1301 picker.set( 'highlight', [ value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] ) 1302 picker.$root.find( '.' + settings.klass.selectYear ).trigger( 'focus' ) 1303 } 1304 }) 1305 }, 1 ). 1306 on( 'open', function() { 1307 var includeToday = '' 1308 if ( calendar.disabled( calendar.get('now') ) ) { 1309 includeToday = ':not(.' + settings.klass.buttonToday + ')' 1310 } 1311 picker.$root.find( 'button' + includeToday + ', select' ).attr( 'disabled', false ) 1312 }, 1 ). 1313 on( 'close', function() { 1314 picker.$root.find( 'button, select' ).attr( 'disabled', true ) 1315 }, 1 ) 1316 1317} //DatePicker 1318 1319 1320/** 1321 * Set a datepicker item object. 1322 */ 1323DatePicker.prototype.set = function( type, value, options ) { 1324 1325 var calendar = this, 1326 calendarItem = calendar.item 1327 1328 // If the value is `null` just set it immediately. 1329 if ( value === null ) { 1330 if ( type == 'clear' ) type = 'select' 1331 calendarItem[ type ] = value 1332 return calendar 1333 } 1334 1335 // Otherwise go through the queue of methods, and invoke the functions. 1336 // Update this as the time unit, and set the final value as this item. 1337 // * In the case of `enable`, keep the queue but set `disable` instead. 1338 // And in the case of `flip`, keep the queue but set `enable` instead. 1339 calendarItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) { 1340 value = calendar[ method ]( type, value, options ) 1341 return value 1342 }).pop() 1343 1344 // Check if we need to cascade through more updates. 1345 if ( type == 'select' ) { 1346 calendar.set( 'highlight', calendarItem.select, options ) 1347 } 1348 else if ( type == 'highlight' ) { 1349 calendar.set( 'view', calendarItem.highlight, options ) 1350 } 1351 else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) { 1352 if ( calendarItem.select && calendar.disabled( calendarItem.select ) ) { 1353 calendar.set( 'select', calendarItem.select, options ) 1354 } 1355 if ( calendarItem.highlight && calendar.disabled( calendarItem.highlight ) ) { 1356 calendar.set( 'highlight', calendarItem.highlight, options ) 1357 } 1358 } 1359 1360 return calendar 1361} //DatePicker.prototype.set 1362 1363 1364/** 1365 * Get a datepicker item object. 1366 */ 1367DatePicker.prototype.get = function( type ) { 1368 return this.item[ type ] 1369} //DatePicker.prototype.get 1370 1371 1372/** 1373 * Create a picker date object. 1374 */ 1375DatePicker.prototype.create = function( type, value, options ) { 1376 1377 var isInfiniteValue, 1378 calendar = this 1379 1380 // If thereâs no value, use the type as the value. 1381 value = value === undefined ? type : value 1382 1383 1384 // If itâs infinity, update the value. 1385 if ( value == -Infinity || value == Infinity ) { 1386 isInfiniteValue = value 1387 } 1388 1389 // If itâs an object, use the native date object. 1390 else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) { 1391 value = value.obj 1392 } 1393 1394 // If itâs an array, convert it into a date and make sure 1395 // that itâs a valid date â otherwise default to today. 1396 else if ( $.isArray( value ) ) { 1397 value = new Date( value[ 0 ], value[ 1 ], value[ 2 ] ) 1398 value = _.isDate( value ) ? value : calendar.create().obj 1399 } 1400 1401 // If itâs a number or date object, make a normalized date. 1402 else if ( _.isInteger( value ) || _.isDate( value ) ) { 1403 value = calendar.normalize( new Date( value ), options ) 1404 } 1405 1406 // If itâs a literal true or any other case, set it to now. 1407 else /*if ( value === true )*/ { 1408 value = calendar.now( type, value, options ) 1409 } 1410 1411 // Return the compiled object. 1412 return { 1413 year: isInfiniteValue || value.getFullYear(), 1414 month: isInfiniteValue || value.getMonth(), 1415 date: isInfiniteValue || value.getDate(), 1416 day: isInfiniteValue || value.getDay(), 1417 obj: isInfiniteValue || value, 1418 pick: isInfiniteValue || value.getTime() 1419 } 1420} //DatePicker.prototype.create 1421 1422 1423/** 1424 * Create a range limit object using an array, date object, 1425 * literal âtrueâ, or integer relative to another time. 1426 */ 1427DatePicker.prototype.createRange = function( from, to ) { 1428 1429 var calendar = this, 1430 createDate = function( date ) { 1431 if ( date === true || $.isArray( date ) || _.isDate( date ) ) { 1432 return calendar.create( date ) 1433 } 1434 return date 1435 } 1436 1437 // Create objects if possible. 1438 if ( !_.isInteger( from ) ) { 1439 from = createDate( from ) 1440 } 1441 if ( !_.isInteger( to ) ) { 1442 to = createDate( to ) 1443 } 1444 1445 // Create relative dates. 1446 if ( _.isInteger( from ) && $.isPlainObject( to ) ) { 1447 from = [ to.year, to.month, to.date + from ]; 1448 } 1449 else if ( _.isInteger( to ) && $.isPlainObject( from ) ) { 1450 to = [ from.year,
1450from.month, from.date + to ]; 1451 } 1452 1453 return { 1454 from: createDate( from ), 1455 to: createDate( to ) 1456 } 1457} //DatePicker.prototype.createRange 1458 1459 1460/** 1461 * Check if a date unit falls within a date range object. 1462 */ 1463DatePicker.prototype.withinRange = function( range, dateUnit ) { 1464 range = this.createRange(range.from, range.to) 1465 return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick 1466} 1467 1468 1469/** 1470 * Check if two date range objects overlap. 1471 */ 1472DatePicker.prototype.overlapRanges = function( one, two ) { 1473 1474 var calendar = this 1475 1476 // Convert the ranges into comparable dates. 1477 one = calendar.createRange( one.from, one.to ) 1478 two = calendar.createRange( two.from, two.to ) 1479 1480 return calendar.withinRange( one, two.from ) || calendar.withinRange( one, two.to ) || 1481 calendar.withinRange( two, one.from ) || calendar.withinRange( two, one.to ) 1482} 1483 1484 1485/** 1486 * Get the date today. 1487 */ 1488DatePicker.prototype.now = function( type, value, options ) { 1489 value = new Date() 1490 if ( options && options.rel ) { 1491 value.setDate( value.getDate() + options.rel ) 1492 } 1493 return this.normalize( value, options ) 1494} 1495 1496 1497/** 1498 * Navigate to next/prev month. 1499 */ 1500DatePicker.prototype.navigate = function( type, value, options ) { 1501 1502 var targetDateObject, 1503 targetYear, 1504 targetMonth, 1505 targetDate, 1506 isTargetArray = $.isArray( value ), 1507 isTargetObject = $.isPlainObject( value ), 1508 viewsetObject = this.item.view/*, 1509 safety = 100*/ 1510 1511 1512 if ( isTargetArray || isTargetObject ) { 1513 1514 if ( isTargetObject ) { 1515 targetYear = value.year 1516 targetMonth = value.month 1517 targetDate = value.date 1518 } 1519 else { 1520 targetYear = +value[0] 1521 targetMonth = +value[1] 1522 targetDate = +value[2] 1523 } 1524 1525 // If weâre navigating months but the view is in a different 1526 // month, navigate to the viewâs year and month. 1527 if ( options && options.nav && viewsetObject && viewsetObject.month !== targetMonth ) { 1528 targetYear = viewsetObject.year 1529 targetMonth = viewsetObject.month 1530 } 1531 1532 // Figure out the expected target year and month. 1533 targetDateObject = new Date( targetYear, targetMonth + ( options && options.nav ? options.nav : 0 ), 1 ) 1534 targetYear = targetDateObject.getFullYear() 1535 targetMonth = targetDateObject.getMonth() 1536 1537 // If the month weâre going to doesnât have enough days, 1538 // keep decreasing the date until we reach the monthâs last date. 1539 while ( /*safety &&*/ new Date( targetYear, targetMonth, targetDate ).getMonth() !== targetMonth ) { 1540 targetDate -= 1 1541 /*safety -= 1 1542 if ( !safety ) { 1543 throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.' 1544 }*/ 1545 } 1546 1547 value = [ targetYear, targetMonth, targetDate ] 1548 } 1549 1550 return value 1551} //DatePicker.prototype.navigate 1552 1553 1554/** 1555 * Normalize a date by setting the hours to midnight. 1556 */ 1557DatePicker.prototype.normalize = function( value/*, options*/ ) { 1558 value.setHours( 0, 0, 0, 0 ) 1559 return value 1560} 1561 1562 1563/** 1564 * Measure the range of dates. 1565 */ 1566DatePicker.prototype.measure = function( type, value/*, options*/ ) { 1567 1568 var calendar = this 1569 1570 // If itâs anything false-y, remove the limits. 1571 if ( !value ) { 1572 value = type == 'min' ? -Infinity : Infinity 1573 } 1574 1575 // If itâs a string, parse it. 1576 else if ( typeof value == 'string' ) { 1577 value = calendar.parse( type, value ) 1578 } 1579 1580 // If it's an integer, get a date relative to today. 1581 else if ( _.isInteger( value ) ) { 1582 value = calendar.now( type, value, { rel: value } ) 1583 } 1584 1585 return value 1586} ///DatePicker.prototype.measure 1587 1588 1589/** 1590 * Create a viewset object based on navigation. 1591 */ 1592DatePicker.prototype.viewset = function( type, dateObject/*, options*/ ) { 1593 return this.create([ dateObject.year, dateObject.month, 1 ]) 1594} 1595 1596 1597/** 1598 * Validate a date as enabled and shift if needed. 1599 */ 1600DatePicker.prototype.validate = function( type, dateObject, options ) { 1601 1602 var calendar = this, 1603 1604 // Keep a reference to the original date. 1605 originalDateObject = dateObject, 1606 1607 // Make sure we have an interval. 1608 interval = options && options.interval ? options.interval : 1, 1609 1610 // Check if the calendar enabled dates are inverted. 1611 isFlippedBase = calendar.item.enable === -1, 1612 1613 // Check if we have any enabled dates after/before now. 1614 hasEnabledBeforeTarget, hasEnabledAfterTarget, 1615 1616 // The min & max limits. 1617 minLimitObject = calendar.item.min, 1618 maxLimitObject = calendar.item.max, 1619 1620 // Check if weâve reached the limit during shifting. 1621 reachedMin, reachedMax, 1622 1623 // Check if the calendar is inverted and at least one weekday is enabled. 1624 hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter( function( value ) { 1625 1626 // If thereâs a date, check where it is relative to the target. 1627 if ( $.isArray( value ) ) { 1628 var dateTime = calendar.create( value ).pick 1629 if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true 1630 else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true 1631 } 1632 1633 // Return only integers for enabled weekdays. 1634 return _.isInteger( value ) 1635 }).length/*, 1636 1637 safety = 100*/ 1638 1639 1640 1641 // Cases to validate for: 1642 // [1] Not inverted and date disabled. 1643 // [2] Inverted and some dates enabled. 1644 // [3] Not inverted and out of range. 1645 // 1646 // Cases to **not** validate for: 1647 // ⢠Navigating months. 1648 // ⢠Not inverted and date enabled. 1649 // ⢠Inverted and all dates disabled. 1650 // ⢠..and anything else.
1651 if ( !options || (!options.nav && !options.defaultValue) ) if ( 1652 /* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) || 1653 /* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) || 1654 /* 3 */ ( !isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick) ) 1655 ) { 1656 1657 1658 // When inverted, flip the direction if there arenât any enabled weekdays 1659 // and there are no enabled dates in the direction of the interval. 1660 if ( isFlippedBase && !hasEnabledWeekdays && ( ( !hasEnabledAfterTarget && interval > 0 ) || ( !hasEnabledBeforeTarget && interval < 0 ) ) ) { 1661 interval *= -1 1662 } 1663 1664 1665 // Keep looping until we reach an enabled date. 1666 while ( /*safety &&*/ calendar.disabled( dateObject ) ) { 1667 1668 /*safety -= 1 1669 if ( !safety ) { 1670 throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.' 1671 }*/ 1672 1673 1674 // If weâve looped into the next/prev month with a large interval, return to the original date and flatten the interval. 1675 if ( Math.abs( interval ) > 1 && ( dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month ) ) { 1676 dateObject = originalDateObject 1677 interval = interval > 0 ? 1 : -1 1678 } 1679 1680 1681 // If weâve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit. 1682 if ( dateObject.pick <= minLimitObject.pick ) { 1683 reachedMin = true 1684 interval = 1 1685 dateObject = calendar.create([ 1686 minLimitObject.year, 1687 minLimitObject.month, 1688 minLimitObject.date + (dateObject.pick === minLimitObject.pick ? 0 : -1) 1689 ]) 1690 } 1691 else if ( dateObject.pick >= maxLimitObject.pick ) { 1692 reachedMax = true 1693 interval = -1 1694 dateObject = calendar.create([ 1695 maxLimitObject.year, 1696 maxLimitObject.month, 1697 maxLimitObject.date + (dateObject.pick === maxLimitObject.pick ? 0 : 1) 1698 ]) 1699 } 1700 1701 1702 // If weâve reached both limits, just break out of the loop. 1703 if ( reachedMin && reachedMax ) { 1704 break 1705 } 1706 1707 1708 // Finally, create the shifted date using the interval and keep looping. 1709 dateObject = calendar.create([ dateObject.year, dateObject.month, dateObject.date + interval ]) 1710 } 1711 1712 } //endif 1713 1714 1715 // Return the date object settled on. 1716 return dateObject 1717} //DatePicker.prototype.validate 1718 1719 1720/** 1721 * Check if a date is disabled. 1722 */ 1723DatePicker.prototype.disabled = function( dateToVerify ) { 1724 1725 var 1726 calendar = this, 1727 1728 // Filter through the disabled dates to check if this is one. 1729 isDisabledMatch = calendar.item.disable.filter( function( dateToDisable ) { 1730 1731 // If the date is a number, match the weekday with 0index and `firstDay` check. 1732 if ( _.isInteger( dateToDisable ) ) { 1733 return dateToVerify.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7 1734 } 1735 1736 // If itâs an array or a native JS date, create and match the exact date. 1737 if ( $.isArray( dateToDisable ) || _.isDate( dateToDisable ) ) { 1738 return dateToVerify.pick === calendar.create( dateToDisable ).pick 1739 } 1740 1741 // If itâs an object, match a date within the âfromâ and âtoâ range. 1742 if ( $.isPlainObject( dateToDisable ) ) { 1743 return calendar.withinRange( dateToDisable, dateToVerify ) 1744 } 1745 }) 1746 1747 // If this date matches a disabled date, confirm itâs not inverted. 1748 isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( dateToDisable ) { 1749 return $.isArray( dateToDisable ) && dateToDisable[3] == 'inverted' || 1750 $.isPlainObject( dateToDisable ) && dateToDisable.inverted 1751 }).length 1752 1753 // Check the calendar âenabledâ flag and respectively flip the 1754 // disabled state. Then also check if itâs beyond the min/max limits. 1755 return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch || 1756 dateToVerify.pick < calendar.item.min.pick || 1757 dateToVerify.pick > calendar.item.max.pick 1758 1759} //DatePicker.prototype.disabled 1760 1761 1762/** 1763 * Parse a string into a usable type. 1764 */ 1765DatePicker.prototype.parse = function( type, value, options ) { 1766 1767 var calendar = this, 1768 parsingObject = {} 1769 1770 // If itâs already parsed, weâre good. 1771 if ( !value || typeof value != 'string' ) { 1772 return value 1773 } 1774 1775 // We need a `.format` to parse the value with. 1776 if ( !( options && options.format ) ) { 1777 options = options || {} 1778 options.format = calendar.settings.format 1779 } 1780 1781 // Convert the format into an array and then map through it. 1782 calendar.formats.toArray( options.format ).map( function( label ) { 1783 1784 var 1785 // Grab the formatting label. 1786 formattingLabel = calendar.formats[ label ], 1787 1788 // The format length is from the formatting label function or the 1789 // label length without the escaping exclamation (!) mark. 1790 formatLength = formattingLabel ? _.trigger( formattingLabel, calendar, [ value, parsingObject ] ) : label.replace( /^!/, '' ).length 1791 1792 // If there's a format label, split the value up to the format length. 1793 // Then add it to the parsing object with appropriate label. 1794 if ( formattingLabel ) { 1795 parsingObject[ label ] = value.substr( 0, formatLength ) 1796 } 1797 1798 // Update the value as the substring from format length to end. 1799 value = value.substr( formatLength ) 1800 }) 1801 1802 // Compensate for month 0index. 1803 return [ 1804 parsingObject.yyyy || parsingObject.yy, 1805 +( parsingObject.mm || parsingObject.m ) - 1, 1806 parsingObject.dd || parsingObject.d 1807 ] 1808} //DatePicker.prototype.parse 1809 1810 1811/** 1812 * Various formats to display the object in. 1813 */ 1814DatePicker.prototype.formats = (function() { 1815 1816 // Return the length of the first word in a collection. 1817 function getWordLengthFromCollection( string, collection, dateObject ) { 1818 1819 // Grab the first word from the string. 1820 // Regex pattern from http://stackoverflow.com/q/150033 1821 var word = string.match( /[^\x00-\x7F]+|\w+/ )[ 0 ] 1822 1823 // If there's no month index, add it to the date object 1824 if ( !dateObject.mm && !dateObject.m ) { 1825 dateObject.m = collection.indexOf( word ) + 1 1826 } 1827 1828 // Return the length of the word. 1829 return word.length 1830 } 1831 1832 // Get the length of the first word in a string. 1833 function getFirstWordLength( string ) { 1834 return string.match( /\w+/ )[ 0 ].length 1835 } 1836 1837 return { 1838 1839 d: function( string, dateObject ) { 1840 1841 // If there's string, then get the digits length. 1842 // Otherwise return the selected date. 1843 return string ? _.digits( string ) : dateObject.date 1844 }, 1845 dd: function( string, dateObject ) { 1846 1847 // If there's a string, then the length is always 2. 1848 // Otherwise return the selected date with a leading zero. 1849 return string ? 2 : _.lead( dateObject.date ) 1850 }, 1851 ddd: function( string, dateObject ) { 1852 1853 // If there's a string, then get the length of the first word. 1854 // Otherwise return the short selected weekday. 1855 return string ? getFirstWordLength( string ) : this.settings.weekdaysShort[ dateObject.day ] 1856 }, 1857 dddd: function( string, dateObject ) { 1858 1859 // If there's a string, then get the length of the first word. 1860 // Otherwise return the full selected weekday. 1861 return string ? getFirstWordLength( string ) : this.settings.weekdaysFull[ dateObject.day ] 1862 }, 1863 m: function( string, dateObject ) { 1864 1865 // If there's a string, then get the length of the digits 1866 // Otherwise return the selected month with 0index compensation. 1867 return string ? _.digits( string ) : dateObject.month + 1 1868 }, 1869 mm: function( string, dateObject ) { 1870 1871 // If there's a string, then the length is always 2. 1872 // Otherwise return the selected month with 0index and leading zero. 1873 return string ? 2 : _.lead( dateObject.month + 1 ) 1874 }, 1875 mmm: function( string, dateObject ) { 1876 1877 var collection = this.settings.monthsShort 1878 1879 // If there's a string, get length of the relevant month from the short 1880 // months collection. Otherwise return the selected month from that c
1880ollection. 1881 return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] 1882 }, 1883 mmmm: function( string, dateObject ) { 1884 1885 var collection = this.settings.monthsFull 1886 1887 // If there's a string, get length of the relevant month from the full 1888 // months collection. Otherwise return the selected month from that collection. 1889 return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] 1890 }, 1891 yy: function( string, dateObject ) { 1892 1893 // If there's a string, then the length is always 2. 1894 // Otherwise return the selected year by slicing out the first 2 digits. 1895 return string ? 2 : ( '' + dateObject.year ).slice( 2 ) 1896 }, 1897 yyyy: function( string, dateObject ) { 1898 1899 // If there's a string, then the length is always 4. 1900 // Otherwise return the selected year. 1901 return string ? 4 : dateObject.year 1902 }, 1903 1904 // Create an array by splitting the formatting string passed. 1905 toArray: function( formatString ) { return formatString.split( /(d{1,4}|m{1,4}|y{4}|yy|!.)/g ) }, 1906 1907 // Format an object into a string using the formatting options. 1908 toString: function ( formatString, itemObject ) { 1909 var calendar = this 1910 return calendar.formats.toArray( formatString ).map( function( label ) { 1911 return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' ) 1912 }).join( '' ) 1913 } 1914 } 1915})() //DatePicker.prototype.formats 1916 1917 1918 1919 1920/** 1921 * Check if two date units are the exact. 1922 */ 1923DatePicker.prototype.isDateExact = function( one, two ) { 1924 1925 var calendar = this 1926 1927 // When weâre working with weekdays, do a direct comparison. 1928 if ( 1929 ( _.isInteger( one ) && _.isInteger( two ) ) || 1930 ( typeof one == 'boolean' && typeof two == 'boolean' ) 1931 ) { 1932 return one === two 1933 } 1934 1935 // When weâre working with date representations, compare the âpickâ value. 1936 if ( 1937 ( _.isDate( one ) || $.isArray( one ) ) && 1938 ( _.isDate( two ) || $.isArray( two ) ) 1939 ) { 1940 return calendar.create( one ).pick === calendar.create( two ).pick 1941 } 1942 1943 // When weâre working with range objects, compare the âfromâ and âtoâ. 1944 if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) { 1945 return calendar.isDateExact( one.from, two.from ) && calendar.isDateExact( one.to, two.to ) 1946 } 1947 1948 return false 1949} 1950 1951 1952/** 1953 * Check if two date units overlap. 1954 */ 1955DatePicker.prototype.isDateOverlap = function( one, two ) { 1956 1957 var calendar = this, 1958 firstDay = calendar.settings.firstDay ? 1 : 0 1959 1960 // When weâre working with a weekday index, compare the days. 1961 if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) { 1962 one = one % 7 + firstDay 1963 return one === calendar.create( two ).day + 1 1964 } 1965 if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) { 1966 two = two % 7 + firstDay 1967 return two === calendar.create( one ).day + 1 1968 } 1969 1970 // When weâre working with range objects, check if the ranges overlap. 1971 if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) { 1972 return calendar.overlapRanges( one, two ) 1973 } 1974 1975 return false 1976} 1977 1978 1979/** 1980 * Flip the âenabledâ state. 1981 */ 1982DatePicker.prototype.flipEnable = function(val) { 1983 var itemObject = this.item 1984 itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1) 1985} 1986 1987 1988/** 1989 * Mark a collection of dates as âdisabledâ. 1990 */ 1991DatePicker.prototype.deactivate = function( type, datesToDisable ) { 1992 1993 var calendar = this, 1994 disabledItems = calendar.item.disable.slice(0) 1995 1996 1997 // If weâre flipping, thatâs all we need to do. 1998 if ( datesToDisable == 'flip' ) { 1999 calendar.flipEnable() 2000 } 2001 2002 else if ( datesToDisable === false ) { 2003 calendar.flipEnable(1) 2004 disabledItems = [] 2005 } 2006 2007 else if ( datesToDisable === true ) { 2008 calendar.flipEnable(-1) 2009 disabledItems = [] 2010 } 2011 2012 // Otherwise go through the dates to disable. 2013 else { 2014 2015 datesToDisable.map(function( unitToDisable ) { 2016 2017 var matchFound 2018 2019 // When we have disabled items, check for matches. 2020 // If something is matched, immediately break out. 2021 for ( var index = 0; index < disabledItems.length; index += 1 ) { 2022 if ( calendar.isDateExact( unitToDisable, disabledItems[index] ) ) { 2023 matchFound = true 2024 break 2025 } 2026 } 2027 2028 // If nothing was found, add the validated unit to the collection. 2029 if ( !matchFound ) { 2030 if ( 2031 _.isInteger( unitToDisable ) || 2032 _.isDate( unitToDisable ) || 2033 $.isArray( unitToDisable ) || 2034 ( $.isPlainObject( unitToDisable ) && unitToDisable.from && unitToDisable.to ) 2035 ) { 2036 disabledItems.push( unitToDisable ) 2037 } 2038 } 2039 }) 2040 } 2041 2042 // Return the updated collection. 2043 return disabledItems 2044} //DatePicker.prototype.deactivate 2045 2046 2047/** 2048 * Mark a collection of dates as âenabledâ. 2049 */ 2050DatePicker.prototype.activate = function( type, datesToEnable ) { 2051 2052 var calendar = this, 2053 disabledItems = calendar.item.disable, 2054 disabledItemsCount = disabledItems.length 2055 2056 // If weâre flipping, thatâs all we need to do. 2057 if ( datesToEnable == 'flip' ) { 2058 calendar.flipEnable() 2059 } 2060 2061 else if ( datesToEnable === true ) { 2062 calendar.flipEnable(1) 2063 disabledItems = [] 2064 } 2065 2066 else if ( datesToEnable === false ) { 2067 calendar.flipEnable(-1) 2068 disabledItems = [] 2069 } 2070 2071 // Otherwise go through the disabled dates. 2072 else { 2073 2074 datesToEnable.map(function( unitToEnable ) { 2075 2076 var matchFound, 2077 disabledUnit, 2078 index, 2079 isExactRange 2080 2081 // Go through the disabled items and try to find a match. 2082 for ( index = 0; index < disabledItemsCount; index += 1 ) { 2083 2084 disabledUnit = disabledItems[index] 2085 2086 // When an exact match is found, remove it from the collection. 2087 if ( calendar.isDateExact( disabledUnit, unitToEnable ) ) { 2088 matchFound = disabledItems[index] = null 2089 isExactRange = true 2090 break 2091 } 2092 2093 // When an overlapped match is found, add the âinvertedâ state to it. 2094 else if ( calendar.isDateOverlap( disabledUnit, unitToEnable ) ) { 2095 if ( $.isPlainObject( unitToEnable ) ) { 2096 unitToEnable.inverted = true 2097 matchFound = unitToEnable 2098 } 2099 else if ( $.isArray( unitToEnable ) ) { 2100 matchFound = unitToEnable 2101 if ( !matchFound[3] ) matchFound.push( '
2101inverted' ) 2102 } 2103 else if ( _.isDate( unitToEnable ) ) { 2104 matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ] 2105 } 2106 break 2107 } 2108 } 2109 2110 // If a match was found, remove a previous duplicate entry. 2111 if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) { 2112 if ( calendar.isDateExact( disabledItems[index], unitToEnable ) ) { 2113 disabledItems[index] = null 2114 break 2115 } 2116 } 2117 2118 // In the event that weâre dealing with an exact range of dates, 2119 // make sure there are no âinvertedâ dates because of it. 2120 if ( isExactRange ) for ( index = 0; index < disabledItemsCount; index += 1 ) { 2121 if ( calendar.isDateOverlap( disabledItems[index], unitToEnable ) ) { 2122 disabledItems[index] = null 2123 break 2124 } 2125 } 2126 2127 // If something is still matched, add it into the collection. 2128 if ( matchFound ) { 2129 disabledItems.push( matchFound ) 2130 } 2131 }) 2132 } 2133 2134 // Return the updated collection. 2135 return disabledItems.filter(function( val ) { return val != null }) 2136} //DatePicker.prototype.activate 2137 2138 2139/** 2140 * Create a string for the nodes in the picker. 2141 */ 2142DatePicker.prototype.nodes = function( isOpen ) { 2143 2144 var 2145 calendar = this, 2146 settings = calendar.settings, 2147 calendarItem = calendar.item, 2148 nowObject = calendarItem.now, 2149 selectedObject = calendarItem.select, 2150 highlightedObject = calendarItem.highlight, 2151 viewsetObject = calendarItem.view, 2152 disabledCollection = calendarItem.disable, 2153 minLimitObject = calendarItem.min, 2154 maxLimitObject = calendarItem.max, 2155 2156 2157 // Create the calendar table head using a copy of weekday labels collection. 2158 // * We do a copy so we don't mutate the original array. 2159 tableHead = (function( collection, fullCollection ) { 2160 2161 // If the first day should be Monday, move Sunday to the end. 2162 if ( settings.firstDay ) { 2163 collection.push( collection.shift() ) 2164 fullCollection.push( fullCollection.shift() ) 2165 } 2166 2167 // Create and return the table head group. 2168 return _.node( 2169 'thead', 2170 _.node( 2171 'tr', 2172 _.group({ 2173 min: 0, 2174 max: DAYS_IN_WEEK - 1, 2175 i: 1, 2176 node: 'th', 2177 item: function( counter ) { 2178 return [ 2179 collection[ counter ], 2180 settings.klass.weekdays, 2181 'scope=col title="' + fullCollection[ counter ] + '"' 2182 ] 2183 } 2184 }) 2185 ) 2186 ) //endreturn 2187 })( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort ).slice( 0 ), settings.weekdaysFull.slice( 0 ) ), //tableHead 2188 2189 2190 // Create the nav for next/prev month. 2191 createMonthNav = function( next ) { 2192 2193 // Otherwise, return the created month tag. 2194 return _.node( 2195 'div', 2196 ' ', 2197 settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + ( 2198 2199 // If the focused month is outside the range, disabled the button. 2200 ( next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month ) || 2201 ( !next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month ) ? 2202 ' ' + settings.klass.navDisabled : '' 2203 ), 2204 'data-nav=' + ( next || -1 ) + ' ' + 2205 _.ariaAttr({ 2206 role: 'button', 2207 controls: calendar.$node[0].id + '_table' 2208 }) + ' ' + 2209 'title="' + (next ? settings.labelMonthNext : settings.labelMonthPrev ) + '"' 2210 ) //endreturn 2211 }, //createMonthNav 2212 2213 2214 // Create the month label. 2215 createMonthLabel = function() { 2216 2217 var monthsCollection = settings.showMonthsShort ? settings.monthsShort : settings.monthsFull 2218 2219 // If there are months to select, add a dropdown menu. 2220 if ( settings.selectMonths ) { 2221
2222 return _.node( 'select', 2223 _.group({ 2224 min: 0, 2225 max: 11, 2226 i: 1, 2227 node: 'option', 2228 item: function( loopedMonth ) { 2229 2230 return [ 2231 2232 // The looped month and no classes. 2233 monthsCollection[ loopedMonth ], 0, 2234 2235 // Set the value and selected index. 2236 'value=' + loopedMonth + 2237 ( viewsetObject.month == loopedMonth ? ' selected' : '' ) + 2238 ( 2239 ( 2240 ( viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month ) || 2241 ( viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month ) 2242 ) ? 2243 ' disabled' : '' 2244 ) 2245 ] 2246 } 2247 }), 2248 settings.klass.selectMonth, 2249 ( isOpen ? '' : 'disabled' ) + ' ' + 2250 _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + 2251 'title="' + settings.labelMonthSelect + '"' 2252 ) 2253 } 2254 2255 // If there's a need for a month selector 2256 return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month ) 2257 }, //createMonthLabel 2258 2259 2260 // Create the year label. 2261 createYearLabel = function() { 2262 2263 var focusedYear = viewsetObject.year, 2264 2265 // If years selector is set to a literal "true", set it to 5. Otherwise 2266 // divide in half to get half before and half after focused year. 2267 numberYears = settings.selectYears === true ? 5 : ~~( settings.selectYears / 2 ) 2268 2269 // If there are years to select, add a dropdown menu. 2270 if ( numberYears ) { 2271 2272 var 2273 minYear = minLimitObject.year, 2274 maxYear = maxLimitObject.year, 2275 lowestYear = focusedYear - numberYears, 2276 highestYear = focusedYear + numberYears 2277 2278 // If the min year is greater than the lowest year, increase the highest year 2279 // by the difference and set the lowest year to the min year. 2280 if ( minYear > lowestYear ) { 2281 highestYear += minYear - lowestYear 2282 lowestYear = minYear 2283 } 2284 2285 // If the max year is less than the highest year, decrease the lowest year 2286 // by the lower of the two: available and needed years. Then set the 2287 // highest year to the max year. 2288 if ( maxYear < highestYear ) { 2289 2290 var availableYears = lowestYear - minYear, 2291 neededYears = highestYear - maxYear 2292 2293 lowestYear -= availableYears > neededYears ? neededYears : availableYears 2294 highestYear = maxYear 2295 } 2296 2297 return _.node( 'select', 2298 _.group({ 2299 min: lowestYear, 2300 max: highestYear, 2301 i: 1, 2302 node: 'option', 2303 item: function( loopedYear ) { 2304 return [ 2305 2306 // The looped year and no classes. 2307 loopedYear, 0, 2308 2309 // Set the value and selected index. 2310 'value=' + loopedYear + ( focusedYear == loopedYear ? ' selected' : '' ) 2311 ] 2312 } 2313 }), 2314 settings.klass.selectYear, 2315 ( isOpen ? '' : 'disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' + 2316 'title="' + settings.labelYearSelect + '"' 2317 ) 2318 } 2319 2320 // Otherwise just return the year focused
2321 return _.node( 'div', focusedYear, settings.klass.year ) 2322 } //createYearLabel 2323 2324 2325 // Create and return the entire calendar. 2326 return _.node( 2327 'div', 2328 ( settings.selectYears ? createYearLabel() + createMonthLabel() : createMonthLabel() + createYearLabel() ) + 2329 createMonthNav() + createMonthNav( 1 ), 2330 settings.klass.header 2331 ) + _.node( 2332 'table', 2333 tableHead + 2334 _.node( 2335 'tbody', 2336 _.group({ 2337 min: 0, 2338 max: WEEKS_IN_CALENDAR - 1, 2339 i: 1, 2340 node: 'tr', 2341 item: function( rowCounter ) { 2342 2343 // If Monday is the first day and the month starts on Sunday, shift the date back a week. 2344 var shiftDateBy = settings.firstDay && calendar.create([ viewsetObject.year, viewsetObject.month, 1 ]).day === 0 ? -7 : 0 2345 2346 return [ 2347 _.group({ 2348 min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index 2349 max: function() { 2350 return this.min + DAYS_IN_WEEK - 1 2351 }, 2352 i: 1, 2353 node: 'td', 2354 item: function( targetDate ) { 2355 2356 // Convert the time date from a relative date to a target date. 2357 targetDate = calendar.create([ viewsetObject.year, viewsetObject.month, targetDate + ( settings.firstDay ? 1 : 0 ) ]) 2358 2359 var isSelected = selectedObject && selectedObject.pick == targetDate.pick, 2360 isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick, 2361 isDisabled = disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick, 2362 formattedDate = _.trigger( calendar.formats.toString, calendar, [ settings.format, targetDate ] ) 2363 2364 return [ 2365 _.node( 2366 'div', 2367 targetDate.date, 2368 (function( klasses ) { 2369 2370 // Add the `infocus` or `outfocus` classes based on month in view. 2371 klasses.push( viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus ) 2372 2373 // Add the `today` class if needed. 2374 if ( nowObject.pick == targetDate.pick ) { 2375 klasses.push( settings.klass.now ) 2376 } 2377 2378 // Add the `selected` class if something's selected and the time matches. 2379 if ( isSelected ) { 2380 klasses.push( settings.klass.selected ) 2381 } 2382 2383 // Add the `highlighted` class if something's highlighted and the time matches. 2384 if ( isHighlighted ) { 2385 klasses.push( settings.klass.highlighted ) 2386 } 2387 2388 // Add the `disabled` class if something's disabled and the object matches. 2389 if ( isDisabled ) { 2390 klasses.push( settings.klass.disabled ) 2391 } 2392 2393 return klasses.join( ' ' ) 2394 })([ settings.klass.day ]), 2395 'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({ 2396 role: 'gridcell', 2397 label: formattedDate, 2398 selected: isSelected && calendar.$node.val() === formattedDate ? true : null, 2399 activedescendant: isHighlighted ? true : null, 2400 disabled: isDisabled ? true : null 2401 }) 2402 ), 2403 '', 2404 _.ariaAttr({ role: 'presentation' }) 2405 ] //endreturn 2406 } 2407 }) 2408 ] //endreturn 2409 } 2410 }) 2411 ), 2412 settings.klass.table, 2413 'id="' + calendar.$node[0].id + '_table' + '" ' + _.ariaAttr({ 2414 role: 'grid', 2415 controls: calendar.$node[0].id, 2416 readonly: true 2417 }) 2418 ) + 2419 2420 // * For Firefox forms to submit, make sure to set the buttonsâ `type` attributes as âbuttonâ. 2421 _.node( 2422 'div', 2423 _.node( 'button', settings.today, settings.klass.buttonToday,
2424 'type=button data-pick=' + nowObject.pick + 2425 ( isOpen && !calendar.disabled(nowObject) ? '' : ' disabled' ) + ' ' + 2426 _.ariaAttr({ controls: calendar.$node[0].id }) ) + 2427 _.node( 'button', settings.clear, settings.klass.buttonClear, 2428 'type=button data-clear=1' + 2429 ( isOpen ? '' : ' disabled' ) + ' ' + 2430 _.ariaAttr({ controls: calendar.$node[0].id }) ) + 2431 _.node('button', settings.close, settings.klass.buttonClose, 2432 'type=button data-close=true ' + 2433 ( isOpen ? '' : ' disabled' ) + ' ' + 2434 _.ariaAttr({ controls: calendar.$node[0].id }) ), 2435 settings.klass.footer 2436 ) //endreturn 2437} //DatePicker.prototype.nodes 2438 2439 2440 2441 2442/** 2443 * The date picker defaults. 2444 */ 2445DatePicker.defaults = (function( prefix ) { 2446 2447 return { 2448 2449 // The title label to use for the month nav buttons 2450 labelMonthNext: 'Next month', 2451 labelMonthPrev: 'Previous month', 2452 2453 // The title label to use for the dropdown selectors 2454 labelMonthSelect: 'Select a month', 2455 labelYearSelect: 'Select a year', 2456 2457 // Months and weekdays 2458 monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], 2459 monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], 2460 weekdaysFull: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], 2461 weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], 2462 2463 // Today and clear 2464 today: 'Today', 2465 clear: 'Clear', 2466 close: 'Close', 2467 2468 // Picker close behavior 2469 closeOnSelect: true, 2470 closeOnClear: true, 2471 2472 // The format to show on the `input` element 2473 format: 'd mmmm, yyyy', 2474 2475 // Classes 2476 klass: { 2477 2478 table: prefix + 'table', 2479 2480 header: prefix + 'header', 2481 2482 navPrev: prefix + 'nav--prev', 2483 navNext: prefix + 'nav--next', 2484 navDisabled: prefix + 'nav--disabled', 2485 2486 month: prefix + 'month', 2487 year: prefix + 'year', 2488 2489 selectMonth: prefix + 'select--month', 2490 selectYear: prefix + 'select--year', 2491 2492 weekdays: prefix + 'weekday', 2493 2494 day: prefix + 'day', 2495 disabled: prefix + 'day--disabled', 2496 selected: prefix + 'day--selected', 2497 highlighted: prefix + 'day--highlighted', 2498 now: prefix + 'day--today', 2499 infocus: prefix + 'day--infocus', 2500 outfocus: prefix + 'day--outfocus', 2501 2502 footer: prefix + 'footer', 2503 2504 buttonClear: prefix + 'button--clear', 2505 buttonToday: prefix + 'button--today', 2506 buttonClose: prefix + 'button--close' 2507 } 2508 } 2509})( Picker.klasses().picker + '__' ) 2510 2511 2512 2513 2514 2515/** 2516 * Extend the picker to add the date picker. 2517 */ 2518Picker.extend( 'pickadate', DatePicker ) 2519 2520 2521})); 2522 2523 2524 2525/** 2526 * @license AngularJS v1.5.5 2527 * (c) 2010-2016 Google, Inc. http://angularjs.org 2528 * License: MIT 2529 */ 2530(function(window) {'use strict'; 2531 2532/** 2533 * @description 2534 * 2535 * This object provides a utility for producing rich Error messages within 2536 * Angular. It can be called as follows: 2537 * 2538 * var exampleMinErr = minErr('example'); 2539 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 2540 * 2541 * The above creates an instance of minErr in the example namespace. The 2542 * resulting error will have a namespaced error code of example.one. The 2543 * resulting error will replace {0} with the value of foo, and {1} with the 2544 * value of bar. The object is not restricted in the number of arguments it can 2545 * take. 2546 * 2547 * If fewer arguments are specified than necessary for interpolation, the extra 2548 * interpolation markers will be preserved in the final string. 2549 * 2550 * Since data will be parsed statically during a build step, some restrictions 2551 * are applied with respect to how minErr instances are created and called. 2552 * Instances should have names of the form namespaceMinErr for a minErr created 2553 * using minErr('namespace') . Error codes, namespaces and template strings 2554 * should all be static strings, not variables or general expressions. 2555 * 2556 * @param {string} module The namespace to use for the new minErr instance. 2557 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 2558 * error from returned function, for cases when a particular type of error is useful. 2559 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 2560 */ 2561 2562function minErr(module, ErrorConstructor) { 2563 ErrorConstructor = ErrorConstructor || Error; 2564 return function() { 2565 var SKIP_INDEXES = 2; 2566 2567 var templateArgs = arguments, 2568 code = templateArgs[0], 2569 message = '[' + (module ? module + ':' : '') + code + '] ', 2570 template = templateArgs[1], 2571 paramPrefix, i; 2572 2573 message += template.replace(/\{\d+\}/g, function(match) { 2574 var index = +match.slice(1, -1), 2575 shiftedIndex = index + SKIP_INDEXES; 2576 2577 if (shiftedIndex < templateArgs.length) { 2578 return toDebugString(templateArgs[shiftedIndex]); 2579 } 2580 2581 return match; 2582 }); 2583 2584 message += '\nhttp://errors.angularjs.org/1.5.5/' + 2585 (module ? module + '/' : '') + code; 2586 2587 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
2588 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + 2589 encodeURIComponent(toDebugString(templateArgs[i])); 2590 } 2591 2592 return new ErrorConstructor(message); 2593 }; 2594} 2595 2596/* We need to tell jshint what variables are being exported */ 2597/* global angular: true, 2598 msie: true, 2599 jqLite: true, 2600 jQuery: true, 2601 slice: true, 2602 splice: true, 2603 push: true, 2604 toString: true, 2605 ngMinErr: true, 2606 angularModule: true, 2607 uid: true, 2608 REGEX_STRING_REGEXP: true, 2609 VALIDITY_STATE_PROPERTY: true, 2610 2611 lowercase: true, 2612 uppercase: true, 2613 manualLowercase: true, 2614 manualUppercase: true, 2615 nodeName_: true, 2616 isArrayLike: true, 2617 forEach: true, 2618 forEachSorted: true, 2619 reverseParams: true, 2620 nextUid: true, 2621 setHashKey: true, 2622 extend: true, 2623 toInt: true, 2624 inherit: true, 2625 merge: true, 2626 noop: true, 2627 identity: true, 2628 valueFn: true, 2629 isUndefined: true, 2630 isDefined: true, 2631 isObject: true, 2632 isBlankObject: true, 2633 isString: true, 2634 isNumber: true, 2635 isDate: true, 2636 isArray: true, 2637 isFunction: true, 2638 isRegExp: true, 2639 isWindow: true, 2640 isScope: true, 2641 isFile: true, 2642 isFormData: true, 2643 isBlob: true, 2644 isBoolean: true, 2645 isPromiseLike: true, 2646 trim: true, 2647 escapeForRegexp: true, 2648 isElement: true, 2649 makeMap: true, 2650 includes: true, 2651 arrayRemove: true, 2652 copy: true, 2653 shallowCopy: true, 2654 equals: true, 2655 csp: true, 2656 jq: true, 2657 concat: true, 2658 sliceArgs: true, 2659 bind: true, 2660 toJsonReplacer: true, 2661 toJson: true, 2662 fromJson: true, 2663 convertTimezoneToLocal: true, 2664 timezoneToOffset: true, 2665 startingTag: true, 2666 tryDecodeURIComponent: true, 2667 parseKeyValue: true, 2668 toKeyValue: true, 2669 encodeUriSegment: true, 2670 encodeUriQuery: true, 2671 angularInit: true, 2672 bootstrap: true, 2673 getTestability: true, 2674 snake_case: true, 2675 bindJQuery: true, 2676 assertArg: true, 2677 assertArgFn: true, 2678 assertNotHasOwnProperty: true, 2679 getter: true, 2680 getBlockNodes: true, 2681 hasOwnProperty: true, 2682 createMap: true, 2683 2684 NODE_TYPE_ELEMENT: true, 2685 NODE_TYPE_ATTRIBUTE: true, 2686 NODE_TYPE_TEXT: true, 2687 NODE_TYPE_COMMENT: true, 2688 NODE_TYPE_DOCUMENT: true, 2689 NODE_TYPE_DOCUMENT_FRAGMENT: true, 2690*/ 2691 2692//////////////////////////////////// 2693 2694/** 2695 * @ngdoc module 2696 * @name ng 2697 * @module ng 2698 * @installation 2699 * @description 2700 * 2701 * # ng (core module) 2702 * The ng module is loaded by default when an AngularJS application is started. The module itself 2703 * contains the essential components for an AngularJS application to function. The table below 2704 * lists a high level breakdown of each of the services/factories, filters, directives and testing 2705 * components available within this core module. 2706 * 2707 * <div doc-module-components="ng"></div> 2708 */ 2709 2710var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 2711 2712// The name of a form control's ValidityState property. 2713// This is used so that it's possible for internal tests to create mock ValidityStates. 2714var VALIDITY_STATE_PROPERTY = 'validity'; 2715 2716var hasOwnProperty = Object.prototype.hasOwnProperty; 2717 2718var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 2719var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 2720 2721 2722var manualLowercase = function(s) { 2723 /* jshint bitwise: false */ 2724 return isString(s) 2725 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 2726 : s; 2727}; 2728var manualUppercase = function(s) { 2729 /* jshint bitwise: false */ 2730 return isString(s) 2731 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 2732 : s; 2733}; 2734 2735 2736// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 2737// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 2738// with correct but slower alternatives. See https://github.com/angular/angular.js/issues/11387 2739if ('i' !== 'I'.toLowerCase()) { 2740 lowercase = manualLowercase; 2741 uppercase = manualUppercase; 2742} 2743 2744 2745var 2746 msie, // holds major version number for IE, or NaN if UA is not IE. 2747 jqLite, // delay binding since jQuery could be loaded after us. 2748 jQuery, // delay binding 2749 slice = [].slice, 2750 splice = [].splice, 2751 push = [].push, 2752 toString = Object.prototype.toString, 2753 getPrototypeOf = Object.getPrototypeOf, 2754 ngMinErr = minErr('ng'), 2755 2756 /** @name angular */ 2757 angular = window.angular || (window.angular = {}), 2758 angularModule, 2759 uid = 0; 2760 2761/** 2762 * documentMode is an IE-only property
2763 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 2764 */ 2765msie = window.document.documentMode; 2766 2767 2768/** 2769 * @private 2770 * @param {*} obj 2771 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 2772 * String ...) 2773 */ 2774function isArrayLike(obj) { 2775 2776 // `null`, `undefined` and `window` are not array-like 2777 if (obj == null || isWindow(obj)) return false; 2778 2779 // arrays, strings and jQuery/jqLite objects are array like 2780 // * jqLite is either the jQuery or jqLite constructor function 2781 // * we have to check the existence of jqLite first as this method is called 2782 // via the forEach method when constructing the jqLite object in the first place 2783 if (isArray(obj) || isString(obj) || (jqLite && obj instanceof jqLite)) return true; 2784 2785 // Support: iOS 8.2 (not reproducible in simulator) 2786 // "length" in obj used to prevent JIT error (gh-11508) 2787 var length = "length" in Object(obj) && obj.length; 2788 2789 // NodeList objects (with `item` method) and 2790 // other objects with suitable length characteristics are array-like 2791 return isNumber(length) && 2792 (length >= 0 && ((length - 1) in obj || obj instanceof Array) || typeof obj.item == 'function'); 2793 2794} 2795 2796/** 2797 * @ngdoc function 2798 * @name angular.forEach 2799 * @module ng 2800 * @kind function 2801 * 2802 * @description 2803 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 2804 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 2805 * is the value of an object property or an array element, `key` is the object property key or 2806 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 2807 * 2808 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 2809 * using the `hasOwnProperty` method. 2810 * 2811 * Unlike ES262's 2812 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 2813 * providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 2814 * return the value provided. 2815 * 2816 ```js 2817 var values = {name: 'misko', gender: 'male'}; 2818 var log = []; 2819 angular.forEach(values, function(value, key) { 2820 this.push(key + ': ' + value); 2821 }, log); 2822 expect(log).toEqual(['name: misko', 'gender: male']); 2823 ``` 2824 * 2825 * @param {Object|Array} obj Object to iterate over. 2826 * @param {Function} iterator Iterator function. 2827 * @param {Object=} context Object to become context (`this`) for the iterator function. 2828 * @returns {Object|Array} Reference to `obj`. 2829 */ 2830 2831function forEach(obj, iterator, context) { 2832 var key, length; 2833 if (obj) { 2834 if (isFunction(obj)) { 2835 for (key in obj) { 2836 // Need to check if hasOwnProperty exists, 2837 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 2838 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 2839 iterator.call(context, obj[key], key, obj); 2840 } 2841 } 2842 } else if (isArray(obj) || isArrayLike(obj)) { 2843 var isPrimitive = typeof obj !== 'object'; 2844 for (key = 0, length = obj.length; key < length; key++) { 2845 if (isPrimitive || key in obj) { 2846 iterator.call(context, obj[key], key, obj); 2847 } 2848 } 2849 } else if (obj.forEach && obj.forEach !== forEach) { 2850 obj.forEach(iterator, context, obj); 2851 } else if (isBlankObject(obj)) { 2852 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 2853 for (key in obj) { 2854 iterator.call(context, obj[key], key, obj); 2855 } 2856 } else if (typeof obj.hasOwnProperty === 'function') { 2857 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 2858 for (key in obj) { 2859 if (obj.hasOwnProperty(key)) { 2860 iterator.call(context, obj[key], key, obj); 2861 } 2862 } 2863 } else { 2864 // Slow path for objects which do not have a method `hasOwnProperty` 2865 for (key in obj) { 2866 if (hasOwnProperty.call(obj, key)) { 2867 iterator.call(context, obj[key], key, obj); 2868 } 2869 } 2870 } 2871 } 2872 return obj; 2873} 2874 2875function forEachSorted(obj, iterator, context) { 2876 var keys = Object.keys(obj).sort(); 2877 for (var i = 0; i < keys.length; i++) { 2878 iterator.call(context, obj[keys[i]], keys[i]); 2879 } 2880 return keys; 2881} 2882 2883 2884/**
vendor: 10,551 bytes, lines 2885-3333
2885 * when using forEach the params are value, key, but it is often useful to have key, value. 2886 * @param {function(string, *)} iteratorFn 2887 * @returns {function(*, string)} 2888 */ 2889function reverseParams(iteratorFn) { 2890 return function(value, key) {iteratorFn(key, value);}; 2891} 2892 2893/** 2894 * A consistent way of creating unique IDs in angular. 2895 * 2896 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 2897 * we hit number precision issues in JavaScript. 2898 * 2899 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 2900 * 2901 * @returns {number} an unique alpha-numeric string 2902 */ 2903function nextUid() { 2904 return ++uid; 2905} 2906 2907 2908/** 2909 * Set or clear the hashkey for an object. 2910 * @param obj object 2911 * @param h the hashkey (!truthy to delete the hashkey) 2912 */ 2913function setHashKey(obj, h) { 2914 if (h) { 2915 obj.$$hashKey = h; 2916 } else { 2917 delete obj.$$hashKey; 2918 } 2919} 2920 2921 2922function baseExtend(dst, objs, deep) { 2923 var h = dst.$$hashKey; 2924 2925 for (var i = 0, ii = objs.length; i < ii; ++i) { 2926 var obj = objs[i]; 2927 if (!isObject(obj) && !isFunction(obj)) continue; 2928 var keys = Object.keys(obj); 2929 for (var j = 0, jj = keys.length; j < jj; j++) { 2930 var key = keys[j]; 2931 var src = obj[key]; 2932 2933 if (deep && isObject(src)) { 2934 if (isDate(src)) { 2935 dst[key] = new Date(src.valueOf()); 2936 } else if (isRegExp(src)) { 2937 dst[key] = new RegExp(src); 2938 } else if (src.nodeName) { 2939 dst[key] = src.cloneNode(true); 2940 } else if (isElement(src)) { 2941 dst[key] = src.clone(); 2942 } else { 2943 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 2944 baseExtend(dst[key], [src], true); 2945 } 2946 } else { 2947 dst[key] = src; 2948 } 2949 } 2950 } 2951 2952 setHashKey(dst, h); 2953 return dst; 2954} 2955 2956/** 2957 * @ngdoc function 2958 * @name angular.extend 2959 * @module ng 2960 * @kind function 2961 * 2962 * @description 2963 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 2964 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 2965 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 2966 * 2967 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 2968 * {@link angular.merge} for this. 2969 * 2970 * @param {Object} dst Destination object. 2971 * @param {...Object} src Source object(s). 2972 * @returns {Object} Reference to `dst`. 2973 */ 2974function extend(dst) { 2975 return baseExtend(dst, slice.call(arguments, 1), false); 2976} 2977 2978 2979/** 2980* @ngdoc function 2981* @name angular.merge 2982* @module ng 2983* @kind function 2984* 2985* @description 2986* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 2987* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 2988* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 2989* 2990* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 2991* objects, performing a deep copy. 2992* 2993* @param {Object} dst Destination object. 2994* @param {...Object} src Source object(s). 2995* @returns {Object} Reference to `dst`. 2996*/ 2997function merge(dst) { 2998 return baseExtend(dst, slice.call(arguments, 1), true); 2999} 3000 3001 3002 3003function toInt(str) { 3004 return parseInt(str, 10); 3005} 3006 3007 3008function inherit(parent, extra) { 3009 return extend(Object.create(parent), extra); 3010} 3011 3012/** 3013 * @ngdoc function 3014 * @name angular.noop 3015 * @module ng 3016 * @kind function 3017 * 3018 * @description 3019 * A function that performs no operations. This function can be useful when writing code in the 3020 * functional style. 3021 ```js 3022 function foo(callback) { 3023 var result = calculateResult(); 3024 (callback || angular.noop)(result); 3025 } 3026 ``` 3027 */ 3028function noop() {} 3029noop.$inject = []; 3030 3031 3032/** 3033 * @ngdoc function 3034 * @name angular.identity 3035 * @module ng 3036 * @kind function 3037 * 3038 * @description 3039 * A function that returns its first argument. This function is useful when writing code in the 3040 * functional style. 3041 * 3042 ```js 3043 function transformer(transformationFn, value) { 3044 return (transformationFn || angular.identity)(value); 3045 }; 3046 ``` 3047 * @param {*} value to be returned. 3048 * @returns {*} the value passed in. 3049 */ 3050function identity($) {return $;} 3051identity.$inject = []; 3052 3053 3054function valueFn(value) {return function valueRef() {return value;};} 3055 3056function hasCustomToString(obj) { 3057 return isFunction(obj.toString) && obj.toString !== toString; 3058} 3059 3060 3061/** 3062 * @ngdoc function 3063 * @name angular.isUndefined 3064 * @module ng 3065 * @kind function 3066 * 3067 * @description 3068 * Determines if a reference is undefined. 3069 * 3070 * @param {*} value Reference to check. 3071 * @returns {boolean} True if `value` is undefined. 3072 */ 3073function isUndefined(value) {return typeof value === 'undefined';} 3074 3075 3076/** 3077 * @ngdoc function 3078 * @name angular.isDefined 3079 * @module ng 3080 * @kind function 3081 * 3082 * @description 3083 * Determines if a reference is defined. 3084 * 3085 * @param {*} value Reference to check. 3086 * @returns {boolean} True if `value` is defined. 3087 */ 3088function isDefined(value) {return typeof value !== 'undefined';} 3089 3090 3091/** 3092 * @ngdoc function 3093 * @name angular.isObject 3094 * @module ng 3095 * @kind function 3096 * 3097 * @description 3098 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 3099 * considered to be objects. Note that JavaScript arrays are objects. 3100 * 3101 * @param {*} value Reference to check. 3102 * @returns {boolean} True if `value` is an `Object` but not `null`. 3103 */ 3104function isObject(value) { 3105 // http://jsperf.com/isobject4 3106 return value !== null && typeof value === 'object'; 3107} 3108 3109 3110/** 3111 * Determine if a value is an object with a null prototype 3112 * 3113 * @returns {boolean} True if `value` is an `Object` with a null prototype 3114 */ 3115function isBlankObject(value) { 3116 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 3117} 3118 3119 3120/** 3121 * @ngdoc function 3122 * @name angular.isString 3123 * @module ng 3124 * @kind function 3125 * 3126 * @description 3127 * Determines if a reference is a `String`. 3128 * 3129 * @param {*} value Reference to check. 3130 * @returns {boolean} True if `value` is a `String`. 3131 */ 3132function isString(value) {return typeof value === 'string';} 3133 3134 3135/** 3136 * @ngdoc function 3137 * @name angular.isNumber 3138 * @module ng 3139 * @kind function 3140 * 3141 * @description 3142 * Determines if a reference is a `Number`. 3143 * 3144 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 3145 * 3146 * If you wish to exclude these then you can use the native 3147 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 3148 * method. 3149 * 3150 * @param {*} value Reference to check. 3151 * @returns {boolean} True if `value` is a `Number`. 3152 */ 3153function isNumber(value) {return typeof value === 'number';} 3154 3155 3156/** 3157 * @ngdoc function 3158 * @name angular.isDate 3159 * @module ng 3160 * @kind function 3161 * 3162 * @description 3163 * Determines if a value is a date. 3164 * 3165 * @param {*} value Reference to check. 3166 * @returns {boolean} True if `value` is a `Date`. 3167 */ 3168function isDate(value) { 3169 return toString.call(value) === '[object Date]'; 3170} 3171 3172 3173/** 3174 * @ngdoc function 3175 * @name angular.isArray 3176 * @module ng 3177 * @kind function 3178 * 3179 * @description 3180 * Determines if a reference is an `Array`. 3181 * 3182 * @param {*} value Reference to check. 3183 * @returns {boolean} True if `value` is an `Array`. 3184 */ 3185var isArray = Array.isArray; 3186 3187/** 3188 * @ngdoc function 3189 * @name angular.isFunction 3190 * @module ng 3191 * @kind function 3192 * 3193 * @description 3194 * Determines if a reference is a `Function`. 3195 * 3196 * @param {*} value Reference to check. 3197 * @returns {boolean} True if `value` is a `Function`. 3198 */ 3199function isFunction(value) {return typeof value === 'function';} 3200 3201 3202/** 3203 * Determines if a value is a regular expression object. 3204 * 3205 * @private 3206 * @param {*} value Reference to check. 3207 * @returns {boolean} True if `value` is a `RegExp`. 3208 */ 3209function isRegExp(value) { 3210 return toString.call(value) === '[object RegExp]'; 3211} 3212 3213 3214/** 3215 * Checks if `obj` is a window object. 3216 * 3217 * @private 3218 * @param {*} obj Object to check 3219 * @returns {boolean} True if `obj` is a window obj. 3220 */ 3221function isWindow(obj) { 3222 return obj && obj.window === obj; 3223} 3224 3225 3226function isScope(obj) { 3227 return obj && obj.$evalAsync && obj.$watch; 3228} 3229 3230 3231function isFile(obj) { 3232 return toString.call(obj) === '[object File]'; 3233} 3234 3235 3236function isFormData(obj) { 3237 return toString.call(obj) === '[object FormData]'; 3238} 3239 3240 3241function isBlob(obj) { 3242 return toString.call(obj) === '[object Blob]'; 3243} 3244 3245 3246function isBoolean(value) { 3247 return typeof value === 'boolean'; 3248} 3249 3250 3251function isPromiseLike(obj) { 3252 return obj && isFunction(obj.then); 3253} 3254 3255 3256var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/; 3257function isTypedArray(value) { 3258 return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value)); 3259} 3260 3261function isArrayBuffer(obj) { 3262 return toString.call(obj) === '[object ArrayBuffer]'; 3263} 3264 3265 3266var trim = function(value) { 3267 return isString(value) ? value.trim() : value; 3268}; 3269 3270// Copied from: 3271// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 3272// Prereq: s is a string. 3273var escapeForRegexp = function(s) { 3274 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 3275 replace(/\x08/g, '\\x08'); 3276}; 3277 3278 3279/** 3280 * @ngdoc function 3281 * @name angular.isElement 3282 * @module ng 3283 * @kind function 3284 * 3285 * @description 3286 * Determines if a reference is a DOM element (or wrapped jQuery element). 3287 * 3288 * @param {*} value Reference to check. 3289 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 3290 */ 3291function isElement(node) { 3292 return !!(node && 3293 (node.nodeName // we are a direct element 3294 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 3295} 3296 3297/** 3298 * @param str 'key1,key2,...' 3299 * @returns {object} in the form of {key1:true, key2:true, ...} 3300 */ 3301function makeMap(str) { 3302 var obj = {}, items = str.split(','), i; 3303 for (i = 0; i < items.length; i++) { 3304 obj[items[i]] = true; 3305 } 3306 return obj; 3307} 3308 3309 3310function nodeName_(element) { 3311 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 3312} 3313 3314function includes(array, obj) { 3315 return Array.prototype.indexOf.call(array, obj) != -1; 3316} 3317 3318function arrayRemove(array, value) { 3319 var index = array.indexOf(value); 3320 if (index >= 0) { 3321 array.splice(index, 1); 3322 } 3323 return index; 3324} 3325 3326/** 3327 * @ngdoc function 3328 * @name angular.copy 3329 * @module ng 3330 * @kind function 3331 * 3332 * @description 3333 * Creates a deep copy of `source`, which should be an object or an array.
3334 * 3335 * * If no destination is supplied, a copy of the object or array is created. 3336 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 3337 * are deleted and then all elements/properties from the source are copied to it. 3338 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 3339 * * If `source` is identical to 'destination' an exception will be thrown. 3340 * 3341 * @param {*} source The source that will be used to make a copy. 3342 * Can be any type, including primitives, `null`, and `undefined`. 3343 * @param {(Object|Array)=} destination Destination into which the source is copied. If 3344 * provided, must be of the same type as `source`. 3345 * @returns {*} The copy or updated `destination`, if `destination` was specified. 3346 * 3347 * @example 3348 <example module="copyExample"> 3349 <file name="index.html"> 3350 <div ng-controller="ExampleController"> 3351 <form novalidate class="simple-form"> 3352 Name: <input type="text" ng-model="user.name" /><br /> 3353 E-mail: <input type="email" ng-model="user.email" /><br /> 3354 Gender: <input type="radio" ng-model="user.gender" value="male" />male 3355 <input type="radio" ng-model="user.gender" value="female" />female<br /> 3356 <button ng-click="reset()">RESET</button> 3357 <button ng-click="update(user)">SAVE</button> 3358 </form> 3359 <pre>form = {{user | json}}</pre> 3360 <pre>master = {{master | json}}</pre> 3361 </div> 3362 3363 <script> 3364 angular.module('copyExample', []) 3365 .controller('ExampleController', ['$scope', function($scope) { 3366 $scope.master= {}; 3367 3368 $scope.update = function(user) { 3369 // Example with 1 argument 3370 $scope.master= angular.copy(user); 3371 }; 3372 3373 $scope.reset = function() { 3374 // Example with 2 arguments 3375 angular.copy($scope.master, $scope.user); 3376 }; 3377 3378 $scope.reset(); 3379 }]); 3380 </script> 3381 </file> 3382 </example> 3383 */ 3384function copy(source, destination) { 3385 var stackSource = []; 3386 var stackDest = []; 3387 3388 if (destination) { 3389 if (isTypedArray(destination) || isArrayBuffer(destination)) { 3390 throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated."); 3391 } 3392 if (source === destination) { 3393 throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); 3394 } 3395 3396 // Empty the destination object 3397 if (isArray(destination)) { 3398 destination.length = 0; 3399 } else { 3400 forEach(destination, function(value, key) { 3401 if (key !== '$$hashKey') { 3402 delete destination[key]; 3403 } 3404 }); 3405 } 3406 3407 stackSource.push(source); 3408 stackDest.push(destination); 3409 return copyRecurse(source, destination); 3410 } 3411 3412 return copyElement(source); 3413 3414 function copyRecurse(source, destination) { 3415 var h = destination.$$hashKey; 3416 var key; 3417 if (isArray(source)) { 3418 for (var i = 0, ii = source.length; i < ii; i++) { 3419 destination.push(copyElement(source[i])); 3420 } 3421 } else if (isBlankObject(source)) { 3422 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 3423 for (key in source) { 3424 destination[key] = copyElement(source[key]); 3425 } 3426 } else if (source && typeof source.hasOwnProperty === 'function') { 3427 // Slow path, which must rely on hasOwnProperty 3428 for (key in source) { 3429 if (source.hasOwnProperty(key)) { 3430 destination[key] = copyElement(source[key]); 3431 } 3432 } 3433 } else { 3434 // Slowest path --- hasOwnProperty can't be called as a method 3435 for (key in source) { 3436 if (hasOwnProperty.call(source, key)) { 3437 destination[key] = copyElement(source[key]); 3438 } 3439 } 3440 } 3441 setHashKey(destination, h); 3442 return destination; 3443 } 3444 3445 function copyElement(source) { 3446 // Simple values 3447 if (!isObject(source)) { 3448 return source; 3449 } 3450 3451 // Already copied values 3452 var index = stackSource.indexOf(source); 3453 if (index !== -1) { 3454 return stackDest[index]; 3455 } 3456 3457 if (isWindow(source) || isScope(source)) { 3458 throw ngMinErr('cpws', 3459 "Can't copy! Making copies of Window or Scope instances is not supported."); 3460 } 3461 3462 var needsRecurse = false;
vendor: 3,388 bytes, lines 3463-3578
3463 var destination = copyType(source); 3464 3465 if (destination === undefined) { 3466 destination = isArray(source) ? [] : Object.create(getPrototypeOf(source)); 3467 needsRecurse = true; 3468 } 3469 3470 stackSource.push(source); 3471 stackDest.push(destination); 3472 3473 return needsRecurse 3474 ? copyRecurse(source, destination) 3475 : destination; 3476 } 3477 3478 function copyType(source) { 3479 switch (toString.call(source)) { 3480 case '[object Int8Array]': 3481 case '[object Int16Array]': 3482 case '[object Int32Array]': 3483 case '[object Float32Array]': 3484 case '[object Float64Array]': 3485 case '[object Uint8Array]': 3486 case '[object Uint8ClampedArray]': 3487 case '[object Uint16Array]': 3488 case '[object Uint32Array]': 3489 return new source.constructor(copyElement(source.buffer)); 3490 3491 case '[object ArrayBuffer]': 3492 //Support: IE10 3493 if (!source.slice) { 3494 var copied = new ArrayBuffer(source.byteLength); 3495 new Uint8Array(copied).set(new Uint8Array(source)); 3496 return copied; 3497 } 3498 return source.slice(0); 3499 3500 case '[object Boolean]': 3501 case '[object Number]': 3502 case '[object String]': 3503 case '[object Date]': 3504 return new source.constructor(source.valueOf()); 3505 3506 case '[object RegExp]': 3507 var re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 3508 re.lastIndex = source.lastIndex; 3509 return re; 3510 3511 case '[object Blob]': 3512 return new source.constructor([source], {type: source.type}); 3513 } 3514 3515 if (isFunction(source.cloneNode)) { 3516 return source.cloneNode(true); 3517 } 3518 } 3519} 3520 3521/** 3522 * Creates a shallow copy of an object, an array or a primitive. 3523 * 3524 * Assumes that there are no proto properties for objects. 3525 */ 3526function shallowCopy(src, dst) { 3527 if (isArray(src)) { 3528 dst = dst || []; 3529 3530 for (var i = 0, ii = src.length; i < ii; i++) { 3531 dst[i] = src[i]; 3532 } 3533 } else if (isObject(src)) { 3534 dst = dst || {}; 3535 3536 for (var key in src) { 3537 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 3538 dst[key] = src[key]; 3539 } 3540 } 3541 } 3542 3543 return dst || src; 3544} 3545 3546 3547/** 3548 * @ngdoc function 3549 * @name angular.equals 3550 * @module ng 3551 * @kind function 3552 * 3553 * @description 3554 * Determines if two objects or two values are equivalent. Supports value types, regular 3555 * expressions, arrays and objects. 3556 * 3557 * Two objects or values are considered equivalent if at least one of the following is true: 3558 * 3559 * * Both objects or values pass `===` comparison. 3560 * * Both objects or values are of the same type and all of their properties are equal by 3561 * comparing them with `angular.equals`. 3562 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 3563 * * Both values represent the same regular expression (In JavaScript, 3564 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 3565 * representation matches). 3566 * 3567 * During a property comparison, properties of `function` type and properties with names 3568 * that begin with `$` are ignored. 3569 * 3570 * Scope and DOMWindow objects are being compared only by identify (`===`). 3571 * 3572 * @param {*} o1 Object or value to compare. 3573 * @param {*} o2 Object or value to compare. 3574 * @returns {boolean} True if arguments are equal. 3575 * 3576 * @example 3577 <example module="equalsExample" name="equalsExample"> 3578 <file name="index.html">
3579 <div ng-controller="ExampleController"> 3580 <form novalidate> 3581 <h3>User 1</h3> 3582 Name: <input type="text" ng-model="user1.name"> 3583 Age: <input type="number" ng-model="user1.age"> 3584 3585 <h3>User 2</h3> 3586 Name: <input type="text" ng-model="user2.name"> 3587 Age: <input type="number" ng-model="user2.age"> 3588 3589 <div> 3590 <br/> 3591 <input type="button" value="Compare" ng-click="compare()"> 3592 </div> 3593 User 1: <pre>{{user1 | json}}</pre> 3594 User 2: <pre>{{user2 | json}}</pre> 3595 Equal: <pre>{{result}}</pre> 3596 </form> 3597 </div> 3598 </file> 3599 <file name="script.js"> 3600 angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) { 3601 $scope.user1 = {}; 3602 $scope.user2 = {}; 3603 $scope.result; 3604 $scope.compare = function() { 3605 $scope.result = angular.equals($scope.user1, $scope.user2); 3606 }; 3607 }]); 3608 </file> 3609 </example> 3610 */ 3611function equals(o1, o2) { 3612 if (o1 === o2) return true; 3613 if (o1 === null || o2 === null) return false; 3614 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 3615 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 3616 if (t1 == t2 && t1 == 'object') { 3617 if (isArray(o1)) { 3618 if (!isArray(o2)) return false; 3619 if ((length = o1.length) == o2.length) { 3620 for (key = 0; key < length; key++) { 3621 if (!equals(o1[key], o2[key])) return false; 3622 } 3623 return true; 3624 } 3625 } else if (isDate(o1)) { 3626 if (!isDate(o2)) return false; 3627 return equals(o1.getTime(), o2.getTime()); 3628 } else if (isRegExp(o1)) { 3629 if (!isRegExp(o2)) return false; 3630 return o1.toString() == o2.toString(); 3631 } else { 3632 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 3633 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 3634 keySet = createMap(); 3635 for (key in o1) { 3636 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 3637 if (!equals(o1[key], o2[key])) return false; 3638 keySet[key] = true; 3639 } 3640 for (key in o2) { 3641 if (!(key in keySet) && 3642 key.charAt(0) !== '$' && 3643 isDefined(o2[key]) && 3644 !isFunction(o2[key])) return false; 3645 } 3646 return true; 3647 } 3648 } 3649 return false; 3650} 3651 3652var csp = function() { 3653 if (!isDefined(csp.rules)) { 3654 3655 3656 var ngCspElement = (window.document.querySelector('[ng-csp]') || 3657 window.document.querySelector('[data-ng-csp]')); 3658 3659 if (ngCspElement) { 3660 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 3661 ngCspElement.getAttribute('data-ng-csp'); 3662 csp.rules = { 3663 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 3664 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 3665 }; 3666 } else { 3667 csp.rules = { 3668 noUnsafeEval: noUnsafeEval(), 3669 noInlineStyle: false 3670 }; 3671 } 3672 } 3673 3674 return csp.rules; 3675 3676 function noUnsafeEval() { 3677 try { 3678 /* jshint -W031, -W054 */ 3679 new Function(''); 3680 /* jshint +W031, +W054 */ 3681 return false; 3682 } catch (e) { 3683 return true; 3684 } 3685 } 3686}; 3687 3688/** 3689 * @ngdoc directive 3690 * @module ng 3691 * @name ngJq 3692 * 3693 * @element ANY 3694 * @param {string=} ngJq the name of the library available under `window` 3695 * to be used for angular.element 3696 * @description 3697 * Use this directive to force the angular.element library. This should be 3698 * used to force either jqLite by leaving ng-jq blank or setting the name of 3699 * the jquery variable under window (eg. jQuery). 3700 * 3701 * Since angular looks for this directive when it is loaded (doesn't wait for the 3702 * DOMContentLoaded event), it must be placed on an element that comes before the script 3703 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 3704 * others ignored. 3705 * 3706 * @example 3707 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 3708 ```html 3709 <!doctype html> 3710 <html ng-app ng-jq> 3711 ... 3712 ... 3713 </html> 3714 ``` 3715 * @example 3716 * This example shows how to use a jQuery based library of a different name. 3717 * The library name must be available at the top most 'window'. 3718 ```html 3719 <!doctype html> 3720 <html ng-app ng-jq="jQueryLib"> 3721 ... 3722 ... 3723 </html> 3724 ``` 3725 */ 3726var jq = function() { 3727 if (isDefined(jq.name_)) return jq.name_; 3728 var el; 3729 var i, ii = ngAttrPrefixes.length, prefix, name; 3730 for (i = 0; i < ii; ++i) { 3731 prefix = ngAttrPrefixes[i]; 3732 if (el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 3733 name = el.getAttribute(prefix + 'jq'); 3734 break; 3735 } 3736 } 3737 3738 return (jq.name_ = name); 3739}; 3740 3741function concat(array1, array2, index) { 3742 return array1.concat(slice.call(array2, index)); 3743} 3744 3745function sliceArgs(args, startIndex) { 3746 return slice.call(args, startIndex || 0); 3747} 3748 3749
vendor: 9,222 bytes, lines 3750-4038
3750/* jshint -W101 */ 3751/** 3752 * @ngdoc function 3753 * @name angular.bind 3754 * @module ng 3755 * @kind function 3756 * 3757 * @description 3758 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 3759 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 3760 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 3761 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 3762 * 3763 * @param {Object} self Context which `fn` should be evaluated in. 3764 * @param {function()} fn Function to be bound. 3765 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 3766 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 3767 */ 3768/* jshint +W101 */ 3769function bind(self, fn) { 3770 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 3771 if (isFunction(fn) && !(fn instanceof RegExp)) { 3772 return curryArgs.length 3773 ? function() { 3774 return arguments.length 3775 ? fn.apply(self, concat(curryArgs, arguments, 0)) 3776 : fn.apply(self, curryArgs); 3777 } 3778 : function() { 3779 return arguments.length 3780 ? fn.apply(self, arguments) 3781 : fn.call(self); 3782 }; 3783 } else { 3784 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 3785 return fn; 3786 } 3787} 3788 3789 3790function toJsonReplacer(key, value) { 3791 var val = value; 3792 3793 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 3794 val = undefined; 3795 } else if (isWindow(value)) { 3796 val = '$WINDOW'; 3797 } else if (value && window.document === value) { 3798 val = '$DOCUMENT'; 3799 } else if (isScope(value)) { 3800 val = '$SCOPE'; 3801 } 3802 3803 return val; 3804} 3805 3806 3807/** 3808 * @ngdoc function 3809 * @name angular.toJson 3810 * @module ng 3811 * @kind function 3812 * 3813 * @description 3814 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 3815 * stripped since angular uses this notation internally. 3816 * 3817 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 3818 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 3819 * If set to an integer, the JSON output will contain that many spaces per indentation. 3820 * @returns {string|undefined} JSON-ified string representing `obj`. 3821 */ 3822function toJson(obj, pretty) { 3823 if (isUndefined(obj)) return undefined; 3824 if (!isNumber(pretty)) { 3825 pretty = pretty ? 2 : null; 3826 } 3827 return JSON.stringify(obj, toJsonReplacer, pretty); 3828} 3829 3830 3831/** 3832 * @ngdoc function 3833 * @name angular.fromJson 3834 * @module ng 3835 * @kind function 3836 * 3837 * @description 3838 * Deserializes a JSON string. 3839 * 3840 * @param {string} json JSON string to deserialize. 3841 * @returns {Object|Array|string|number} Deserialized JSON string. 3842 */ 3843function fromJson(json) { 3844 return isString(json) 3845 ? JSON.parse(json) 3846 : json; 3847} 3848 3849 3850var ALL_COLONS = /:/g; 3851function timezoneToOffset(timezone, fallback) { 3852 // IE/Edge do not "understand" colon (`:`) in timezone 3853 timezone = timezone.replace(ALL_COLONS, ''); 3854 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 3855 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 3856} 3857 3858 3859function addDateMinutes(date, minutes) { 3860 date = new Date(date.getTime()); 3861 date.setMinutes(date.getMinutes() + minutes); 3862 return date; 3863} 3864 3865 3866function convertTimezoneToLocal(date, timezone, reverse) { 3867 reverse = reverse ? -1 : 1; 3868 var dateTimezoneOffset = date.getTimezoneOffset(); 3869 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 3870 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 3871} 3872 3873 3874/** 3875 * @returns {string} Returns the string representation of the element. 3876 */ 3877function startingTag(element) { 3878 element = jqLite(element).clone(); 3879 try { 3880 // turns out IE does not let you set .html() on elements which 3881 // are not allowed to have children. So we just ignore it. 3882 element.empty(); 3883 } catch (e) {} 3884 var elemHtml = jqLite('<div>').append(element).html(); 3885 try { 3886 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 3887 elemHtml. 3888 match(/^(<[^>]+>)/)[1]. 3889 replace(/^<([\w\-]+)/, function(match, nodeName) {return '<' + lowercase(nodeName);}); 3890 } catch (e) { 3891 return lowercase(elemHtml); 3892 } 3893 3894} 3895 3896 3897///////////////////////////////////////////////// 3898 3899/** 3900 * Tries to decode the URI component without throwing an exception. 3901 * 3902 * @private 3903 * @param str value potential URI component to check. 3904 * @returns {boolean} True if `value` can be decoded 3905 * with the decodeURIComponent function. 3906 */ 3907function tryDecodeURIComponent(value) { 3908 try { 3909 return decodeURIComponent(value); 3910 } catch (e) { 3911 // Ignore any invalid uri component 3912 } 3913} 3914 3915 3916/** 3917 * Parses an escaped url query string into key-value pairs. 3918 * @returns {Object.<string,boolean|Array>} 3919 */ 3920function parseKeyValue(/**string*/keyValue) { 3921 var obj = {}; 3922 forEach((keyValue || "").split('&'), function(keyValue) { 3923 var splitPoint, key, val; 3924 if (keyValue) { 3925 key = keyValue = keyValue.replace(/\+/g,'%20'); 3926 splitPoint = keyValue.indexOf('='); 3927 if (splitPoint !== -1) { 3928 key = keyValue.substring(0, splitPoint); 3929 val = keyValue.substring(splitPoint + 1); 3930 } 3931 key = tryDecodeURIComponent(key); 3932 if (isDefined(key)) { 3933 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 3934 if (!hasOwnProperty.call(obj, key)) { 3935 obj[key] = val; 3936 } else if (isArray(obj[key])) { 3937 obj[key].push(val); 3938 } else { 3939 obj[key] = [obj[key],val]; 3940 } 3941 } 3942 } 3943 }); 3944 return obj; 3945} 3946 3947function toKeyValue(obj) { 3948 var parts = []; 3949 forEach(obj, function(value, key) { 3950 if (isArray(value)) { 3951 forEach(value, function(arrayValue) { 3952 parts.push(encodeUriQuery(key, true) + 3953 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 3954 }); 3955 } else { 3956 parts.push(encodeUriQuery(key, true) + 3957 (value === true ? '' : '=' + encodeUriQuery(value, true))); 3958 } 3959 }); 3960 return parts.length ? parts.join('&') : ''; 3961} 3962 3963 3964/** 3965 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 3966 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 3967 * segments: 3968 * segment = *pchar 3969 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 3970 * pct-encoded = "%" HEXDIG HEXDIG 3971 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 3972 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 3973 * / "*" / "+" / "," / ";" / "=" 3974 */ 3975function encodeUriSegment(val) { 3976 return encodeUriQuery(val, true). 3977 replace(/%26/gi, '&'). 3978 replace(/%3D/gi, '='). 3979 replace(/%2B/gi, '+'); 3980} 3981 3982 3983/** 3984 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 3985 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 3986 * encoded per http://tools.ietf.org/html/rfc3986: 3987 * query = *( pchar / "/" / "?" ) 3988 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 3989 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 3990 * pct-encoded = "%" HEXDIG HEXDIG 3991 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 3992 * / "*" / "+" / "," / ";" / "=" 3993 */ 3994function encodeUriQuery(val, pctEncodeSpaces) { 3995 return encodeURIComponent(val). 3996 replace(/%40/gi, '@'). 3997 replace(/%3A/gi, ':'). 3998 replace(/%24/g, '$'). 3999 replace(/%2C/gi, ','). 4000 replace(/%3B/gi, ';'). 4001 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 4002} 4003 4004var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 4005 4006function getNgAttribute(element, ngAttr) { 4007 var attr, i, ii = ngAttrPrefixes.length; 4008 for (i = 0; i < ii; ++i) { 4009 attr = ngAttrPrefixes[i] + ngAttr; 4010 if (isString(attr = element.getAttribute(attr))) { 4011 return attr; 4012 } 4013 } 4014 return null; 4015} 4016 4017/** 4018 * @ngdoc directive 4019 * @name ngApp 4020 * @module ng 4021 * 4022 * @element ANY 4023 * @param {angular.Module} ngApp an optional application 4024 * {@link angular.module module} name to load. 4025 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 4026 * created in "strict-di" mode. This means that the application will fail to invoke functions which 4027 * do not use explicit function annotation (and are thus unsuitable for minification), as described 4028 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 4029 * tracking down the root of these bugs. 4030 * 4031 * @description 4032 * 4033 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 4034 * designates the **root element** of the application and is typically placed near the root element 4035 * of the page - e.g. on the `<body>` or `<html>` tags. 4036 * 4037 * There are a few things to keep in mind when using `ngApp`: 4038 * - only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
4038p` 4039 * found in the document will be used to define the root element to auto-bootstrap as an 4040 * application. To run multiple applications in an HTML document you must manually bootstrap them using 4041 * {@link angular.bootstrap} instead. 4042 * - AngularJS applications cannot be nested within each other. 4043 * - Do not use a directive that uses {@link ng.$compile#transclusion transclusion} on the same element as `ngApp`. 4044 * This includes directives such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and 4045 * {@link ngRoute.ngView `ngView`}. 4046 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 4047 * causing animations to stop working and making the injector inaccessible from outside the app. 4048 * 4049 * You can specify an **AngularJS module** to be used as the root module for the application. This 4050 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 4051 * should contain the application code needed or have dependencies on other modules that will 4052 * contain the code. See {@link angular.module} for more information. 4053 * 4054 * In the example below if the `ngApp` directive were not placed on the `html` element then the 4055 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 4056 * would not be resolved to `3`. 4057 * 4058 * `ngApp` is the easiest, and most common way to bootstrap an application. 4059 * 4060 <example module="ngAppDemo"> 4061 <file name="index.html"> 4062 <div ng-controller="ngAppDemoController"> 4063 I can add: {{a}} + {{b}} = {{ a+b }} 4064 </div> 4065 </file> 4066 <file name="script.js"> 4067 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 4068 $scope.a = 1; 4069 $scope.b = 2; 4070 }); 4071 </file> 4072 </example> 4073 * 4074 * Using `ngStrictDi`, you would see something like this: 4075 * 4076 <example ng-app-included="true"> 4077 <file name="index.html"> 4078 <div ng-app="ngAppStrictDemo" ng-strict-di> 4079 <div ng-controller="GoodController1"> 4080 I can add: {{a}} + {{b}} = {{ a+b }} 4081 4082 <p>This renders because the controller does not fail to 4083 instantiate, by using explicit annotation style (see 4084 script.js for details) 4085 </p> 4086 </div> 4087 4088 <div ng-controller="GoodController2"> 4089 Name: <input ng-model="name"><br /> 4090 Hello, {{name}}! 4091 4092 <p>This renders because the controller does not fail to 4093 instantiate, by using explicit annotation style 4094 (see script.js for details) 4095 </p> 4096 </div> 4097 4098 <div ng-controller="BadController"> 4099 I can add: {{a}} + {{b}} = {{ a+b }} 4100 4101 <p>The controller could not be instantiated, due to relying 4102 on automatic function annotations (which are disabled in 4103 strict mode). As such, the content of this section is not 4104 interpolated, and there should be an error in your web console. 4105 </p> 4106 </div> 4107 </div> 4108 </file> 4109 <file name="script.js"> 4110 angular.module('ngAppStrictDemo', []) 4111 // BadController will fail to instantiate, due to relying on automatic function annotation, 4112 // rather than an explicit annotation 4113 .controller('BadController', function($scope) { 4114 $scope.a = 1; 4115 $scope.b = 2; 4116 }) 4117 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 4118 // due to using explicit annotations using the array style and $inject property, respectively. 4119 .controller('GoodController1', ['$scope', function($scope) { 4120 $scope.a = 1; 4121 $scope.b = 2; 4122 }]) 4123 .controller('GoodController2', GoodController2); 4124 function GoodController2($scope) { 4125 $scope.name = "World"; 4126 } 4127 GoodController2.$inject = ['$scope']; 4128 </file> 4129 <file name="style.css"> 4130 div[ng-controller] { 4131 margin-bottom: 1em; 4132 -webkit-border-radius: 4px; 4133 border-radius: 4px; 4134 border: 1px solid; 4135 padding: .5em; 4136 } 4137 div[ng-controller^=Good] { 4138 border-color: #d6e9c6; 4139 background-color: #dff0d8; 4140 color: #3c763d; 4141 } 4142 div[ng-controller^=Bad] { 4143 border-color: #ebccd1; 4144 background-color: #f2dede; 4145 color: #a94442; 4146 margin-bottom: 0; 4147 } 4148 </file> 4149 </example> 4150 */ 4151function angularInit(element, bootstrap) { 4152 var appElement, 4153 module, 4154 config = {}; 4155 4156 // The element `element` has priority over any other element 4157 forEach(ngAttrPrefixes, function(prefix) { 4158 var name = prefix + 'app'; 4159 4160 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 4161 appElement = element; 4162 module = element.getAttribute(name); 4163 } 4164 }); 4165 forEach(ngAttrPrefixes, function(prefix) { 4166 var name = prefix + 'app'; 4167 var candidate; 4168 4169 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 4170 appElement = candidate; 4171 module = candidate.getAttribute(name); 4172 } 4173 }); 4174 if (appElement) { 4175 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 4176 bootstrap(appElement, module ? [module] : [], config); 4177 } 4178} 4179 4180/** 4181 * @ngdoc function 4182 * @name angular.bootstrap 4183 * @module ng 4184 * @description 4185 * Use this function to manually start up angular application. 4186 * 4187 * For more information, see the {@link guide/bootstrap Bootstrap guide}. 4188 * 4189 * Angular will detect if it has been loaded into the browser more than once and only allow the 4190 * first loaded script to be bootstrapped and will report a warning to the browser console for 4191 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 4192 * multiple instances of Angular try to work on the DOM. 4193 * 4194 * <div class="alert alert-warning">
4195 * **Note:** Protractor based end-to-end tests cannot use this function to bootstrap manually. 4196 * They must use {@link ng.directive:ngApp ngApp}. 4197 * </div> 4198 * 4199 * <div class="alert alert-warning"> 4200 * **Note:** Do not bootstrap the app on an element with a directive that uses {@link ng.$compile#transclusion transclusion}, 4201 * such as {@link ng.ngIf `ngIf`}, {@link ng.ngInclude `ngInclude`} and {@link ngRoute.ngView `ngView`}. 4202 * Doing this misplaces the app {@link ng.$rootElement `$rootElement`} and the app's {@link auto.$injector injector}, 4203 * causing animations to stop working and making the injector inaccessible from outside the app. 4204 * </div> 4205 * 4206 * ```html 4207 * <!doctype html> 4208 * <html> 4209 * <body> 4210 * <div ng-controller="WelcomeController"> 4211 * {{greeting}} 4212 * </div> 4213 * 4214 * <script src="angular.js"></script> 4215 * <script> 4216 * var app = angular.module('demo', []) 4217 * .controller('WelcomeController', function($scope) { 4218 * $scope.greeting = 'Welcome!'; 4219 * }); 4220 * angular.bootstrap(document, ['demo']); 4221 * </script> 4222 * </body> 4223 * </html> 4224 * ``` 4225 * 4226 * @param {DOMElement} element DOM element which is the root of angular application. 4227 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 4228 * Each item in the array should be the name of a predefined module or a (DI annotated) 4229 * function that will be invoked by the injector as a `config` block. 4230 * See: {@link angular.module modules} 4231 * @param {Object=} config an object for defining configuration options for the application. The 4232 * following keys are supported: 4233 * 4234 * * `strictDi` - disable automatic function annotation for the application. This is meant to 4235 * assist in finding bugs which break minified code. Defaults to `false`. 4236 * 4237 * @returns {auto.$injector} Returns the newly created injector for this app. 4238 */ 4239function bootstrap(element, modules, config) { 4240 if (!isObject(config)) config = {}; 4241 var defaultConfig = { 4242 strictDi: false 4243 }; 4244 config = extend(defaultConfig, config); 4245 var doBootstrap = function() { 4246 element = jqLite(element); 4247 4248 if (element.injector()) { 4249 var tag = (element[0] === window.document) ? 'document' : startingTag(element); 4250 //Encode angle brackets to prevent input from being sanitized to empty string #8683 4251 throw ngMinErr( 4252 'btstrpd', 4253 "App already bootstrapped with this element '{0}'", 4254 tag.replace(/</,'<').replace(/>/,'>')); 4255 } 4256 4257 modules = modules || []; 4258 modules.unshift(['$provide', function($provide) { 4259 $provide.value('$rootElement', element); 4260 }]); 4261 4262 if (config.debugInfoEnabled) { 4263 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 4264 modules.push(['$compileProvider', function($compileProvider) { 4265 $compileProvider.debugInfoEnabled(true); 4266 }]); 4267 } 4268 4269 modules.unshift('ng'); 4270 var injector = createInjector(modules, config.strictDi); 4271 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 4272 function bootstrapApply(scope, element, compile, injector) { 4273 scope.$apply(function() { 4274 element.data('$injector', injector); 4275 compile(element)(scope); 4276 }); 4277 }] 4278 ); 4279 return injector; 4280 }; 4281 4282 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 4283 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 4284 4285 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 4286 config.debugInfoEnabled = true; 4287 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 4288 } 4289 4290 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 4291 return doBootstrap(); 4292 } 4293 4294 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 4295 angular.resumeBootstrap = function(extraModules) { 4296 forEach(extraModules, function(module) { 4297 modules.push(module); 4298 }); 4299 return doBootstrap(); 4300 }; 4301 4302 if (isFunction(angular.resumeDeferredBootstrap)) { 4303 angular.resumeDeferredBootstrap(); 4304 } 4305} 4306 4307/** 4308 * @ngdoc function 4309 * @name angular.reloadWithDebugInfo 4310 * @module ng 4311 * @description 4312 * Use this function to reload the current application with debug information turned on. 4313 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 4314 * 4315 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 4316 */ 4317function reloadWithDebugInfo() { 4318 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 4319 window.location.reload(); 4320} 4321 4322/** 4323 * @name angular.getTestability 4324 * @module ng 4325 * @description 4326 * Get the testability service for the instance of Angular on the given 4327 * element. 4328 * @param {DOMElement} element DOM element which is the root of angular application. 4329 */ 4330function getTestability(rootElement) { 4331 var injector = angular.element(rootElement).injector(); 4332 if (!injector) { 4333 throw ngMinErr('test', 4334 'no injector found for element argument to getTestability'); 4335 } 4336 return injector.get('$$testability'); 4337} 4338 4339var SNAKE_CASE_REGEXP = /[A-Z]/g; 4340function snake_case(name, separator) { 4341 separator = separator || '_'; 4342 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 4343 return (pos ? separator : '') + letter.toLowerCase(); 4344 }); 4345} 4346 4347var bindJQueryFired = false;
vendor: 8,937 bytes, lines 4348-4632
4348function bindJQuery() { 4349 var originalCleanData; 4350 4351 if (bindJQueryFired) { 4352 return; 4353 } 4354 4355 // bind to jQuery if present; 4356 var jqName = jq(); 4357 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 4358 !jqName ? undefined : // use jqLite 4359 window[jqName]; // use jQuery specified by `ngJq` 4360 4361 // Use jQuery if it exists with proper functionality, otherwise default to us. 4362 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 4363 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 4364 // versions. It will not work for sure with jQuery <1.7, though. 4365 if (jQuery && jQuery.fn.on) { 4366 jqLite = jQuery; 4367 extend(jQuery.fn, { 4368 scope: JQLitePrototype.scope, 4369 isolateScope: JQLitePrototype.isolateScope, 4370 controller: JQLitePrototype.controller, 4371 injector: JQLitePrototype.injector, 4372 inheritedData: JQLitePrototype.inheritedData 4373 }); 4374 4375 // All nodes removed from the DOM via various jQuery APIs like .remove() 4376 // are passed through jQuery.cleanData. Monkey-patch this method to fire 4377 // the $destroy event on all removed nodes. 4378 originalCleanData = jQuery.cleanData; 4379 jQuery.cleanData = function(elems) { 4380 var events; 4381 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 4382 events = jQuery._data(elem, "events"); 4383 if (events && events.$destroy) { 4384 jQuery(elem).triggerHandler('$destroy'); 4385 } 4386 } 4387 originalCleanData(elems); 4388 }; 4389 } else { 4390 jqLite = JQLite; 4391 } 4392 4393 angular.element = jqLite; 4394 4395 // Prevent double-proxying. 4396 bindJQueryFired = true; 4397} 4398 4399/** 4400 * throw error if the argument is falsy. 4401 */ 4402function assertArg(arg, name, reason) { 4403 if (!arg) { 4404 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 4405 } 4406 return arg; 4407} 4408 4409function assertArgFn(arg, name, acceptArrayAnnotation) { 4410 if (acceptArrayAnnotation && isArray(arg)) { 4411 arg = arg[arg.length - 1]; 4412 } 4413 4414 assertArg(isFunction(arg), name, 'not a function, got ' + 4415 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 4416 return arg; 4417} 4418 4419/** 4420 * throw error if the name given is hasOwnProperty 4421 * @param {String} name the name to test 4422 * @param {String} context the context in which the name is used, such as module or directive 4423 */ 4424function assertNotHasOwnProperty(name, context) { 4425 if (name === 'hasOwnProperty') { 4426 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 4427 } 4428} 4429 4430/** 4431 * Return the value accessible from the object by path. Any undefined traversals are ignored 4432 * @param {Object} obj starting object 4433 * @param {String} path path to traverse 4434 * @param {boolean} [bindFnToScope=true] 4435 * @returns {Object} value as accessible by path 4436 */ 4437//TODO(misko): this function needs to be removed 4438function getter(obj, path, bindFnToScope) { 4439 if (!path) return obj; 4440 var keys = path.split('.'); 4441 var key; 4442 var lastInstance = obj; 4443 var len = keys.length; 4444 4445 for (var i = 0; i < len; i++) { 4446 key = keys[i]; 4447 if (obj) { 4448 obj = (lastInstance = obj)[key]; 4449 } 4450 } 4451 if (!bindFnToScope && isFunction(obj)) { 4452 return bind(lastInstance, obj); 4453 } 4454 return obj; 4455} 4456 4457/** 4458 * Return the DOM siblings between the first and last node in the given array. 4459 * @param {Array} array like object 4460 * @returns {Array} the inputted object or a jqLite collection containing the nodes 4461 */ 4462function getBlockNodes(nodes) { 4463 // TODO(perf): update `nodes` instead of creating a new object? 4464 var node = nodes[0]; 4465 var endNode = nodes[nodes.length - 1]; 4466 var blockNodes; 4467 4468 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 4469 if (blockNodes || nodes[i] !== node) { 4470 if (!blockNodes) { 4471 blockNodes = jqLite(slice.call(nodes, 0, i)); 4472 } 4473 blockNodes.push(node); 4474 } 4475 } 4476 4477 return blockNodes || nodes; 4478} 4479 4480 4481/** 4482 * Creates a new object without a prototype. This object is useful for lookup without having to 4483 * guard against prototypically inherited properties via hasOwnProperty. 4484 * 4485 * Related micro-benchmarks: 4486 * - http://jsperf.com/object-create2 4487 * - http://jsperf.com/proto-map-lookup/2 4488 * - http://jsperf.com/for-in-vs-object-keys2 4489 * 4490 * @returns {Object} 4491 */ 4492function createMap() { 4493 return Object.create(null); 4494} 4495 4496var NODE_TYPE_ELEMENT = 1; 4497var NODE_TYPE_ATTRIBUTE = 2; 4498var NODE_TYPE_TEXT = 3; 4499var NODE_TYPE_COMMENT = 8; 4500var NODE_TYPE_DOCUMENT = 9; 4501var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 4502 4503/** 4504 * @ngdoc type 4505 * @name angular.Module 4506 * @module ng 4507 * @description 4508 * 4509 * Interface for configuring angular {@link angular.module modules}. 4510 */ 4511 4512function setupModuleLoader(window) { 4513 4514 var $injectorMinErr = minErr('$injector'); 4515 var ngMinErr = minErr('ng'); 4516 4517 function ensure(obj, name, factory) { 4518 return obj[name] || (obj[name] = factory()); 4519 } 4520 4521 var angular = ensure(window, 'angular', Object); 4522 4523 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 4524 angular.$$minErr = angular.$$minErr || minErr; 4525 4526 return ensure(angular, 'module', function() { 4527 /** @type {Object.<string, angular.Module>} */ 4528 var modules = {}; 4529 4530 /** 4531 * @ngdoc function 4532 * @name angular.module 4533 * @module ng 4534 * @description 4535 * 4536 * The `angular.module` is a global place for creating, registering and retrieving Angular 4537 * modules. 4538 * All modules (angular core or 3rd party) that should be available to an application must be 4539 * registered using this mechanism. 4540 * 4541 * Passing one argument retrieves an existing {@link angular.Module}, 4542 * whereas passing more than one argument creates a new {@link angular.Module} 4543 * 4544 * 4545 * # Module 4546 * 4547 * A module is a collection of services, directives, controllers, filters, and configuration information. 4548 * `angular.module` is used to configure the {@link auto.$injector $injector}. 4549 * 4550 * ```js 4551 * // Create a new module 4552 * var myModule = angular.module('myModule', []); 4553 * 4554 * // register a new service 4555 * myModule.value('appName', 'MyCoolApp'); 4556 * 4557 * // configure existing services inside initialization blocks. 4558 * myModule.config(['$locationProvider', function($locationProvider) { 4559 * // Configure existing providers 4560 * $locationProvider.hashPrefix('!'); 4561 * }]); 4562 * ``` 4563 * 4564 * Then you can create an injector and load your modules like this: 4565 * 4566 * ```js 4567 * var injector = angular.injector(['ng', 'myModule']) 4568 * ``` 4569 * 4570 * However it's more likely that you'll just use 4571 * {@link ng.directive:ngApp ngApp} or 4572 * {@link angular.bootstrap} to simplify this process for you. 4573 * 4574 * @param {!string} name The name of the module to create or retrieve. 4575 * @param {!Array.<string>=} requires If specified then new module is being created. If 4576 * unspecified then the module is being retrieved for further configuration. 4577 * @param {Function=} configFn Optional configuration function for the module. Same as 4578 * {@link angular.Module#config Module#config()}. 4579 * @returns {angular.Module} new module with the {@link angular.Module} api. 4580 */ 4581 return function module(name, requires, configFn) { 4582 var assertNotHasOwnProperty = function(name, context) { 4583 if (name === 'hasOwnProperty') { 4584 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 4585 } 4586 }; 4587 4588 assertNotHasOwnProperty(name, 'module'); 4589 if (requires && modules.hasOwnProperty(name)) { 4590 modules[name] = null; 4591 } 4592 return ensure(modules, name, function() { 4593 if (!requires) { 4594 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 4595 "the module name or forgot to load it. If registering a module ensure that you " + 4596 "specify the dependencies as the second argument.", name); 4597 } 4598 4599 /** @type {!Array.<Array.<*>>} */ 4600 var invokeQueue = []; 4601 4602 /** @type {!Array.<Function>} */ 4603 var configBlocks = []; 4604 4605 /** @type {!Array.<Function>} */ 4606 var runBlocks = []; 4607 4608 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 4609 4610 /** @type {angular.Module} */ 4611 var moduleInstance = { 4612 // Private state 4613 _invokeQueue: invokeQueue, 4614 _configBlocks: configBlocks, 4615 _runBlocks: runBlocks, 4616 4617 /** 4618 * @ngdoc property 4619 * @name angular.Module#requires 4620 * @module ng 4621 * 4622 * @description 4623 * Holds the list of modules which the injector will load before the current module is 4624 * loaded. 4625 */ 4626 requires: requires, 4627 4628 /** 4629 * @ngdoc property 4630 * @name angular.Module#name 4631 * @module ng 4632 *
4633 * @description 4634 * Name of the module. 4635 */ 4636 name: name, 4637 4638 4639 /** 4640 * @ngdoc method 4641 * @name angular.Module#provider 4642 * @module ng 4643 * @param {string} name service name 4644 * @param {Function} providerType Construction function for creating new instance of the 4645 * service. 4646 * @description 4647 * See {@link auto.$provide#provider $provide.provider()}. 4648 */ 4649 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 4650 4651 /** 4652 * @ngdoc method 4653 * @name angular.Module#factory 4654 * @module ng 4655 * @param {string} name service name 4656 * @param {Function} providerFunction Function for creating new instance of the service. 4657 * @description 4658 * See {@link auto.$provide#factory $provide.factory()}. 4659 */ 4660 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 4661 4662 /** 4663 * @ngdoc method 4664 * @name angular.Module#service 4665 * @module ng 4666 * @param {string} name service name 4667 * @param {Function} constructor A constructor function that will be instantiated. 4668 * @description 4669 * See {@link auto.$provide#service $provide.service()}. 4670 */ 4671 service: invokeLaterAndSetModuleName('$provide', 'service'), 4672 4673 /** 4674 * @ngdoc method 4675 * @name angular.Module#value 4676 * @module ng 4677 * @param {string} name service name 4678 * @param {*} object Service instance object. 4679 * @description 4680 * See {@link auto.$provide#value $provide.value()}. 4681 */ 4682 value: invokeLater('$provide', 'value'), 4683 4684 /** 4685 * @ngdoc method 4686 * @name angular.Module#constant 4687 * @module ng 4688 * @param {string} name constant name 4689 * @param {*} object Constant value. 4690 * @description 4691 * Because the constants are fixed, they get applied before other provide methods. 4692 * See {@link auto.$provide#constant $provide.constant()}. 4693 */ 4694 constant: invokeLater('$provide', 'constant', 'unshift'), 4695 4696 /** 4697 * @ngdoc method 4698 * @name angular.Module#decorator 4699 * @module ng 4700 * @param {string} name The name of the service to decorate. 4701 * @param {Function} decorFn This function will be invoked when the service needs to be 4702 * instantiated and should return the decorated service instance. 4703 * @description 4704 * See {@link auto.$provide#decorator $provide.decorator()}. 4705 */ 4706 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 4707 4708 /** 4709 * @ngdoc method 4710 * @name angular.Module#animation 4711 * @module ng 4712 * @param {string} name animation name 4713 * @param {Function} animationFactory Factory function for creating new instance of an 4714 * animation. 4715 * @description 4716 * 4717 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 4718 * 4719 * 4720 * Defines an animation hook that can be later used with 4721 * {@link $animate $animate} service and directives that use this service. 4722 * 4723 * ```js 4724 * module.animation('.animation-name', function($inject1, $inject2) { 4725 * return { 4726 * eventName : function(element, done) { 4727 * //code to run the animation 4728 * //once complete, then run done() 4729 * return function cancellationFunction(element) { 4730 * //code to cancel the animation 4731 * } 4732 * } 4733 * } 4734 * }) 4735 * ``` 4736 * 4737 * See {@link ng.$animateProvider#register $animateProvider.register()} and 4738 * {@link ngAnimate ngAnimate module} for more information. 4739 */ 4740 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 4741 4742 /** 4743 * @ngdoc method 4744 * @name angular.Module#filter 4745 * @module ng 4746 * @param {string} name Filter name - this must be a valid angular expression identifier 4747 * @param {Function} filterFactory Factory function for creating new instance of filter.
4748 * @description 4749 * See {@link ng.$filterProvider#register $filterProvider.register()}. 4750 * 4751 * <div class="alert alert-warning"> 4752 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 4753 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 4754 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 4755 * (`myapp_subsection_filterx`). 4756 * </div> 4757 */ 4758 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 4759 4760 /** 4761 * @ngdoc method 4762 * @name angular.Module#controller 4763 * @module ng 4764 * @param {string|Object} name Controller name, or an object map of controllers where the 4765 * keys are the names and the values are the constructors. 4766 * @param {Function} constructor Controller constructor function. 4767 * @description 4768 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 4769 */ 4770 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 4771 4772 /** 4773 * @ngdoc method 4774 * @name angular.Module#directive 4775 * @module ng 4776 * @param {string|Object} name Directive name, or an object map of directives where the 4777 * keys are the names and the values are the factories. 4778 * @param {Function} directiveFactory Factory function for creating new instance of 4779 * directives. 4780 * @description 4781 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 4782 */ 4783 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 4784 4785 /** 4786 * @ngdoc method 4787 * @name angular.Module#component 4788 * @module ng 4789 * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp) 4790 * @param {Object} options Component definition object (a simplified 4791 * {@link ng.$compile#directive-definition-object directive definition object}) 4792 * 4793 * @description 4794 * See {@link ng.$compileProvider#component $compileProvider.component()}. 4795 */ 4796 component: invokeLaterAndSetModuleName('$compileProvider', 'component'), 4797 4798 /** 4799 * @ngdoc method 4800 * @name angular.Module#config 4801 * @module ng 4802 * @param {Function} configFn Execute this function on module load. Useful for service 4803 * configuration. 4804 * @description 4805 * Use this method to register work which needs to be performed on module loading. 4806 * For more about how to configure services, see 4807 * {@link providers#provider-recipe Provider Recipe}. 4808 */ 4809 config: config, 4810 4811 /** 4812 * @ngdoc method 4813 * @name angular.Module#run 4814 * @module ng 4815 * @param {Function} initializationFn Execute this function after injector creation. 4816 * Useful for application initialization. 4817 * @description 4818 * Use this method to register work which should be performed when the injector is done 4819 * loading all modules. 4820 */ 4821 run: function(block) { 4822 runBlocks.push(block); 4823 return this; 4824 } 4825 }; 4826 4827 if (configFn) { 4828 config(configFn); 4829 } 4830 4831 return moduleInstance; 4832 4833 /** 4834 * @param {string} provider 4835 * @param {string} method 4836 * @param {String=} insertMethod 4837 * @returns {angular.Module} 4838 */ 4839 function invokeLater(provider, method, insertMethod, queue) { 4840 if (!queue) queue = invokeQueue; 4841 return function() { 4842 queue[insertMethod || 'push']([provider, method, arguments]); 4843 return moduleInstance; 4844 }; 4845 } 4846 4847 /** 4848 * @param {string} provider 4849 * @param {string} method 4850 * @returns {angular.Module} 4851 */
vendor: 4,714 bytes, lines 4852-5051
4852 function invokeLaterAndSetModuleName(provider, method) { 4853 return function(recipeName, factoryFunction) { 4854 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 4855 invokeQueue.push([provider, method, arguments]); 4856 return moduleInstance; 4857 }; 4858 } 4859 }); 4860 }; 4861 }); 4862 4863} 4864 4865/* global: toDebugString: true */ 4866 4867function serializeObject(obj) { 4868 var seen = []; 4869 4870 return JSON.stringify(obj, function(key, val) { 4871 val = toJsonReplacer(key, val); 4872 if (isObject(val)) { 4873 4874 if (seen.indexOf(val) >= 0) return '...'; 4875 4876 seen.push(val); 4877 } 4878 return val; 4879 }); 4880} 4881 4882function toDebugString(obj) { 4883 if (typeof obj === 'function') { 4884 return obj.toString().replace(/ \{[\s\S]*$/, ''); 4885 } else if (isUndefined(obj)) { 4886 return 'undefined'; 4887 } else if (typeof obj !== 'string') { 4888 return serializeObject(obj); 4889 } 4890 return obj; 4891} 4892 4893/* global angularModule: true, 4894 version: true, 4895 4896 $CompileProvider, 4897 4898 htmlAnchorDirective, 4899 inputDirective, 4900 inputDirective, 4901 formDirective, 4902 scriptDirective, 4903 selectDirective, 4904 styleDirective, 4905 optionDirective, 4906 ngBindDirective, 4907 ngBindHtmlDirective, 4908 ngBindTemplateDirective, 4909 ngClassDirective, 4910 ngClassEvenDirective, 4911 ngClassOddDirective, 4912 ngCloakDirective, 4913 ngControllerDirective, 4914 ngFormDirective, 4915 ngHideDirective, 4916 ngIfDirective, 4917 ngIncludeDirective, 4918 ngIncludeFillContentDirective, 4919 ngInitDirective, 4920 ngNonBindableDirective, 4921 ngPluralizeDirective, 4922 ngRepeatDirective, 4923 ngShowDirective, 4924 ngStyleDirective, 4925 ngSwitchDirective, 4926 ngSwitchWhenDirective, 4927 ngSwitchDefaultDirective, 4928 ngOptionsDirective, 4929 ngTranscludeDirective, 4930 ngModelDirective, 4931 ngListDirective, 4932 ngChangeDirective, 4933 patternDirective, 4934 patternDirective, 4935 requiredDirective, 4936 requiredDirective, 4937 minlengthDirective, 4938 minlengthDirective, 4939 maxlengthDirective, 4940 maxlengthDirective, 4941 ngValueDirective, 4942 ngModelOptionsDirective, 4943 ngAttributeAliasDirectives, 4944 ngEventDirectives, 4945 4946 $AnchorScrollProvider, 4947 $AnimateProvider, 4948 $CoreAnimateCssProvider, 4949 $$CoreAnimateJsProvider, 4950 $$CoreAnimateQueueProvider, 4951 $$AnimateRunnerFactoryProvider, 4952 $$AnimateAsyncRunFactoryProvider, 4953 $BrowserProvider, 4954 $CacheFactoryProvider, 4955 $ControllerProvider, 4956 $DateProvider, 4957 $DocumentProvider, 4958 $ExceptionHandlerProvider, 4959 $FilterProvider, 4960 $$ForceReflowProvider, 4961 $InterpolateProvider, 4962 $IntervalProvider, 4963 $$HashMapProvider, 4964 $HttpProvider, 4965 $HttpParamSerializerProvider, 4966 $HttpParamSerializerJQLikeProvider, 4967 $HttpBackendProvider, 4968 $xhrFactoryProvider, 4969 $LocationProvider, 4970 $LogProvider, 4971 $ParseProvider, 4972 $RootScopeProvider, 4973 $QProvider, 4974 $$QProvider, 4975 $$SanitizeUriProvider, 4976 $SceProvider, 4977 $SceDelegateProvider, 4978 $SnifferProvider, 4979 $TemplateCacheProvider, 4980 $TemplateRequestProvider, 4981 $$TestabilityProvider, 4982 $TimeoutProvider, 4983 $$RAFProvider, 4984 $WindowProvider, 4985 $$jqLiteProvider, 4986 $$CookieReaderProvider 4987*/ 4988 4989 4990/** 4991 * @ngdoc object 4992 * @name angular.version 4993 * @module ng 4994 * @description 4995 * An object that contains information about the current AngularJS version. 4996 * 4997 * This object has the following properties: 4998 * 4999 * - `full` â `{string}` â Full version string, such as "0.9.18". 5000 * - `major` â `{number}` â Major version number, such as "0". 5001 * - `minor` â `{number}` â Minor version number, such as "9". 5002 * - `dot` â `{number}` â Dot version number, such as "18". 5003 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 5004 */ 5005var version = { 5006 full: '1.5.5', // all of these placeholder strings will be replaced by grunt's 5007 major: 1, // package task 5008 minor: 5, 5009 dot: 5, 5010 codeName: 'material-conspiration' 5011}; 5012 5013 5014function publishExternalAPI(angular) { 5015 extend(angular, { 5016 'bootstrap': bootstrap, 5017 'copy': copy, 5018 'extend': extend, 5019 'merge': merge, 5020 'equals': equals, 5021 'element': jqLite, 5022 'forEach': forEach, 5023 'injector': createInjector, 5024 'noop': noop, 5025 'bind': bind, 5026 'toJson': toJson, 5027 'fromJson': fromJson, 5028 'identity': identity, 5029 'isUndefined': isUndefined, 5030 'isDefined': isDefined, 5031 'isString': isString, 5032 'isFunction': isFunction, 5033 'isObject': isObject, 5034 'isNumber': isNumber, 5035 'isElement': isElement, 5036 'isArray': isArray, 5037 'version': version, 5038 'isDate': isDate, 5039 'lowercase': lowercase, 5040 'uppercase': uppercase, 5041 'callbacks': {counter: 0}, 5042 'getTestability': getTestability, 5043 '$$minErr': minErr, 5044 '$$csp': csp, 5045 'reloadWithDebugInfo': reloadWithDebugInfo 5046 }); 5047 5048 angularModule = setupModuleLoader(window); 5049 5050 angularModule('ng', ['ngLocale'], ['$provide', 5051 function ngModule($provide) {
5052 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 5053 $provide.provider({ 5054 $$sanitizeUri: $$SanitizeUriProvider 5055 }); 5056 $provide.provider('$compile', $CompileProvider). 5057 directive({ 5058 a: htmlAnchorDirective, 5059 input: inputDirective, 5060 textarea: inputDirective, 5061 form: formDirective, 5062 script: scriptDirective, 5063 select: selectDirective, 5064 style: styleDirective, 5065 option: optionDirective, 5066 ngBind: ngBindDirective, 5067 ngBindHtml: ngBindHtmlDirective, 5068 ngBindTemplate: ngBindTemplateDirective, 5069 ngClass: ngClassDirective, 5070 ngClassEven: ngClassEvenDirective, 5071 ngClassOdd: ngClassOddDirective, 5072 ngCloak: ngCloakDirective, 5073 ngController: ngControllerDirective, 5074 ngForm: ngFormDirective, 5075 ngHide: ngHideDirective, 5076 ngIf: ngIfDirective, 5077 ngInclude: ngIncludeDirective, 5078 ngInit: ngInitDirective, 5079 ngNonBindable: ngNonBindableDirective, 5080 ngPluralize: ngPluralizeDirective, 5081 ngRepeat: ngRepeatDirective, 5082 ngShow: ngShowDirective, 5083 ngStyle: ngStyleDirective, 5084 ngSwitch: ngSwitchDirective, 5085 ngSwitchWhen: ngSwitchWhenDirective, 5086 ngSwitchDefault: ngSwitchDefaultDirective, 5087 ngOptions: ngOptionsDirective, 5088 ngTransclude: ngTranscludeDirective, 5089 ngModel: ngModelDirective, 5090 ngList: ngListDirective, 5091 ngChange: ngChangeDirective, 5092 pattern: patternDirective, 5093 ngPattern: patternDirective, 5094 required: requiredDirective, 5095 ngRequired: requiredDirective, 5096 minlength: minlengthDirective, 5097 ngMinlength: minlengthDirective, 5098 maxlength: maxlengthDirective, 5099 ngMaxlength: maxlengthDirective, 5100 ngValue: ngValueDirective, 5101 ngModelOptions: ngModelOptionsDirective 5102 }). 5103 directive({ 5104 ngInclude: ngIncludeFillContentDirective 5105 }). 5106 directive(ngAttributeAliasDirectives). 5107 directive(ngEventDirectives); 5108 $provide.provider({ 5109 $anchorScroll: $AnchorScrollProvider, 5110 $animate: $AnimateProvider, 5111 $animateCss: $CoreAnimateCssProvider, 5112 $$animateJs: $$CoreAnimateJsProvider, 5113 $$animateQueue: $$CoreAnimateQueueProvider, 5114 $$AnimateRunner: $$AnimateRunnerFactoryProvider, 5115 $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, 5116 $browser: $BrowserProvider, 5117 $cacheFactory: $CacheFactoryProvider, 5118 $controller: $ControllerProvider, 5119 $document: $DocumentProvider, 5120 $exceptionHandler: $ExceptionHandlerProvider, 5121 $filter: $FilterProvider, 5122 $$forceReflow: $$ForceReflowProvider, 5123 $interpolate: $InterpolateProvider, 5124 $interval: $IntervalProvider, 5125 $http: $HttpProvider, 5126 $httpParamSerializer: $HttpParamSerializerProvider, 5127 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 5128 $httpBackend: $HttpBackendProvider, 5129 $xhrFactory: $xhrFactoryProvider, 5130 $location: $LocationProvider, 5131 $log: $LogProvider, 5132 $parse: $ParseProvider, 5133 $rootScope: $RootScopeProvider, 5134 $q: $QProvider, 5135 $$q: $$QProvider, 5136 $sce: $SceProvider, 5137 $sceDelegate: $SceDelegateProvider, 5138 $sniffer: $SnifferProvider, 5139 $templateCache: $TemplateCacheProvider, 5140 $templateRequest: $TemplateRequestProvider, 5141 $$testability: $$TestabilityProvider, 5142 $timeout: $TimeoutProvider, 5143 $window: $WindowProvider, 5144 $$rAF: $$RAFProvider, 5145 $$jqLite: $$jqLiteProvider, 5146 $$HashMap: $$HashMapProvider, 5147 $$cookieReader: $$CookieReaderProvider 5148 }); 5149 } 5150 ]); 5151} 5152 5153/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 5154 * Any commits to this file should be reviewed with security in mind. * 5155 * Changes to this file can potentially create security vulnerabilities. * 5156 * An approval from 2 Core members with history of modifying * 5157 * this file is required. * 5158 * * 5159 * Does the change somehow allow for arbitrary javascript to be executed? * 5160 * Or allows for someone to change the prototype of built-in objects? * 5161 * Or gives undesired access to variables likes document or window? * 5162 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 5163
5164/* global JQLitePrototype: true, 5165 addEventListenerFn: true, 5166 removeEventListenerFn: true, 5167 BOOLEAN_ATTR: true, 5168 ALIASED_ATTR: true, 5169*/ 5170 5171////////////////////////////////// 5172//JQLite 5173////////////////////////////////// 5174 5175/** 5176 * @ngdoc function 5177 * @name angular.element 5178 * @module ng 5179 * @kind function 5180 * 5181 * @description 5182 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 5183 * 5184 * If jQuery is available, `angular.element` is an alias for the 5185 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 5186 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**. 5187 * 5188 * jqLite is a tiny, API-compatible subset of jQuery that allows 5189 * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most 5190 * commonly needed functionality with the goal of having a very small footprint. 5191 * 5192 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the 5193 * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a 5194 * specific version of jQuery if multiple versions exist on the page. 5195 * 5196 * <div class="alert alert-info">**Note:** All element references in Angular are always wrapped with jQuery or 5197 * jqLite (such as the element argument in a directive's compile / link function). They are never raw DOM references.</div> 5198 * 5199 * <div class="alert alert-warning">**Note:** Keep in mind that this function will not find elements 5200 * by tag name / CSS selector. For lookups by tag name, try instead `angular.element(document).find(...)` 5201 * or `$document.find()`, or use the standard DOM APIs, e.g. `document.querySelectorAll()`.</div> 5202 * 5203 * ## Angular's jqLite 5204 * jqLite provides only the following jQuery methods: 5205 * 5206 * - [`addClass()`](http://api.jquery.com/addClass/) 5207 * - [`after()`](http://api.jquery.com/after/) 5208 * - [`append()`](http://api.jquery.com/append/) 5209 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 5210 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 5211 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 5212 * - [`clone()`](http://api.jquery.com/clone/) 5213 * - [`contents()`](http://api.jquery.com/contents/) 5214 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. 5215 * As a setter, does not convert numbers to strings or append 'px', and also does not have automatic property prefixing. 5216 * - [`data()`](http://api.jquery.com/data/) 5217 * - [`detach()`](http://api.jquery.com/detach/) 5218 * - [`empty()`](http://api.jquery.com/empty/) 5219 * - [`eq()`](http://api.jquery.com/eq/) 5220 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 5221 * - [`hasClass()`](http://api.jquery.com/hasClass/) 5222 * - [`html()`](http://api.jquery.com/html/) 5223 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 5224 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 5225 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 5226 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 5227 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 5228 * - [`prepend()`](http://api.jquery.com/prepend/) 5229 * - [`prop()`](http://api.jquery.com/prop/) 5230 * - [`ready()`](http://api.jquery.com/ready/) 5231 * - [`remove()`](http://api.jquery.com/remove/) 5232 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 5233 * - [`removeClass()`](http://api.jquery.com/removeClass/) 5234 * - [`removeData()`](http://api.jquery.com/removeData/) 5235 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 5236 * - [`text()`](http://api.jquery.com/text/) 5237 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 5238 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 5239 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter 5240 * - [`val()`](http://api.jquery.com/val/) 5241 * - [`wrap()`](http://api.jquery.com/wrap/) 5242 * 5243 * ## jQuery/jqLite Extras 5244 * Angular also provides the following additional methods and events to both jQuery and jqLite: 5245 * 5246 * ### Events 5247 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
5247pis and fires this event 5248 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 5249 * element before it is removed. 5250 * 5251 * ### Methods 5252 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 5253 * retrieves controller associated with the `ngController` directive. If `name` is provided as 5254 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 5255 * `'ngModel'`). 5256 * - `injector()` - retrieves the injector of the current element or its parent. 5257 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 5258 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 5259 * be enabled. 5260 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 5261 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 5262 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 5263 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 5264 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 5265 * parent element is reached. 5266 * 5267 * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See 5268 * https://github.com/angular/angular.js/issues/14251 for more information. 5269 * 5270 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 5271 * @returns {Object} jQuery object. 5272 */ 5273 5274JQLite.expando = 'ng339'; 5275 5276var jqCache = JQLite.cache = {}, 5277 jqId = 1, 5278 addEventListenerFn = function(element, type, fn) { 5279 element.addEventListener(type, fn, false); 5280 }, 5281 removeEventListenerFn = function(element, type, fn) { 5282 element.removeEventListener(type, fn, false); 5283 }; 5284 5285/* 5286 * !!! This is an undocumented "private" function !!! 5287 */ 5288JQLite._data = function(node) { 5289 //jQuery always returns an object on cache miss 5290 return this.cache[node[this.expando]] || {}; 5291}; 5292 5293function jqNextId() { return ++jqId; } 5294 5295 5296var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 5297var MOZ_HACK_REGEXP = /^moz([A-Z])/; 5298var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 5299var jqLiteMinErr = minErr('jqLite'); 5300 5301/** 5302 * Converts snake_case to camelCase. 5303 * Also there is special case for Moz prefix starting with upper case letter. 5304 * @param name Name to normalize 5305 */ 5306function camelCase(name) { 5307 return name. 5308 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 5309 return offset ? letter.toUpperCase() : letter; 5310 }). 5311 replace(MOZ_HACK_REGEXP, 'Moz$1'); 5312} 5313 5314var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 5315var HTML_REGEXP = /<|&#?\w+;/; 5316var TAG_NAME_REGEXP = /<([\w:-]+)/; 5317var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 5318 5319var wrapMap = { 5320 'option': [1, '<select multiple="multiple">', '</select>'], 5321 5322 'thead': [1, '<table>', '</table>'], 5323 'col': [2, '<table><colgroup>', '</colgroup></table>'], 5324 'tr': [2, '<table><tbody>', '</tbody></table>'], 5325 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 5326 '_default': [0, "", ""] 5327}; 5328 5329wrapMap.optgroup = wrapMap.option; 5330wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 5331wrapMap.th = wrapMap.td; 5332 5333 5334function jqLiteIsTextNode(html) { 5335 return !HTML_REGEXP.test(html); 5336} 5337 5338function jqLiteAcceptsData(node) { 5339 // The window object can accept data but has no nodeType 5340 // Otherwise we are only interested in elements (1) and documents (9) 5341 var nodeType = node.nodeType; 5342 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 5343} 5344 5345function jqLiteHasData(node) { 5346 for (var key in jqCache[node.ng339]) { 5347 return true; 5348 } 5349 return false; 5350} 5351 5352function jqLiteCleanData(nodes) { 5353 for (var i = 0, ii = nodes.length; i < ii; i++) { 5354 jqLiteRemoveData(nodes[i]); 5355 } 5356} 5357 5358function jqLiteBuildFragment(html, context) { 5359 var tmp, tag, wrap, 5360 fragment = context.createDocumentFragment(), 5361 nodes = [], i; 5362 5363 if (jqLiteIsTextNode(html)) { 5364 // Convert non-html into a text node 5365 nodes.push(context.createTextNode(html)); 5366 } else { 5367 // Convert html into DOM nodes 5368 tmp = tmp || fragment.appendChild(context.createElement("div")); 5369 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 5370 wrap = wrapMap[tag] || wrapMap._default; 5371 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 5372 5373 // Descend through wrappers to the right content 5374 i = wrap[0]; 5375 while (i--) { 5376 tmp = tmp.lastChild; 5377 } 5378 5379 nodes = concat(nodes, tmp.childNodes); 5380 5381 tmp = fragment.firstChild; 5382 tmp.textContent = ""; 5383 } 5384 5385 // Remove wrapper from fragment 5386 fragment.textContent = ""; 5387 fragment.innerHTML = ""; // Clear inner HTML 5388 forEach(nodes, function(node) { 5389 fragment.appendChild(node); 5390 }); 5391 5392 return fragment; 5393} 5394 5395function jqLiteParseHTML(html, context) { 5396 context = context || window.document; 5397 var parsed; 5398 5399 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 5400 return [context.createElement(parsed[1])]; 5401 } 5402 5403 if ((parsed = jqLiteBuildFragment(html, context))) { 5404 return parsed.childNodes; 5405 } 5406 5407 return []; 5408} 5409 5410function jqLiteWrapNode(node, wrapper) { 5411 var parent = node.parentNode; 5412 5413 if (parent) { 5414 parent.replaceChild(wrapper, node); 5415 } 5416 5417 wrapper.appendChild(node); 5418} 5419 5420 5421// IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. 5422var jqLiteContains = window.Node.prototype.contains || function(arg) { 5423 // jshint bitwise: false 5424 return !!(this.compareDocumentPosition(arg) & 16); 5425 // jshint bitwise: true 5426}; 5427 5428///////////////////////////////////////////// 5429function JQLite(element) { 5430 if (element instanceof JQLite) { 5431 return element; 5432 } 5433 5434 var argIsString; 5435 5436 if (isString(element)) { 5437 element = trim(element); 5438 argIsString = true; 5439 } 5440 if (!(this instanceof JQLite)) { 5441 if (argIsString && element.charAt(0) != '<') { 5442 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 5443 } 5444 return new JQLite(element); 5445 } 5446 5447 if (argIsString) { 5448 jqLiteAddNodes(this, jqLiteParseHTML(element)); 5449 } else { 5450 jqLiteAddNodes(this, element); 5451 } 5452} 5453 5454function jqLiteClone(element) { 5455 return element.cloneNode(true); 5456} 5457 5458function jqLiteDealoc(element, onlyDescendants) { 5459 if (!onlyDescendants) jqLiteRemoveData(element); 5460 5461 if (element.querySelectorAll) { 5462 var descendants = element.querySelectorAll('*');
5463 for (var i = 0, l = descendants.length; i < l; i++) { 5464 jqLiteRemoveData(descendants[i]); 5465 } 5466 } 5467} 5468 5469function jqLiteOff(element, type, fn, unsupported) { 5470 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 5471 5472 var expandoStore = jqLiteExpandoStore(element); 5473 var events = expandoStore && expandoStore.events; 5474 var handle = expandoStore && expandoStore.handle; 5475 5476 if (!handle) return; //no listeners registered 5477 5478 if (!type) { 5479 for (type in events) { 5480 if (type !== '$destroy') { 5481 removeEventListenerFn(element, type, handle); 5482 } 5483 delete events[type]; 5484 } 5485 } else { 5486 5487 var removeHandler = function(type) { 5488 var listenerFns = events[type]; 5489 if (isDefined(fn)) { 5490 arrayRemove(listenerFns || [], fn); 5491 } 5492 if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { 5493 removeEventListenerFn(element, type, handle); 5494 delete events[type]; 5495 } 5496 }; 5497 5498 forEach(type.split(' '), function(type) { 5499 removeHandler(type); 5500 if (MOUSE_EVENT_MAP[type]) { 5501 removeHandler(MOUSE_EVENT_MAP[type]); 5502 } 5503 }); 5504 } 5505} 5506 5507function jqLiteRemoveData(element, name) { 5508 var expandoId = element.ng339; 5509 var expandoStore = expandoId && jqCache[expandoId]; 5510 5511 if (expandoStore) { 5512 if (name) { 5513 delete expandoStore.data[name]; 5514 return; 5515 } 5516 5517 if (expandoStore.handle) { 5518 if (expandoStore.events.$destroy) { 5519 expandoStore.handle({}, '$destroy'); 5520 } 5521 jqLiteOff(element); 5522 } 5523 delete jqCache[expandoId]; 5524 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 5525 } 5526} 5527 5528 5529function jqLiteExpandoStore(element, createIfNecessary) { 5530 var expandoId = element.ng339, 5531 expandoStore = expandoId && jqCache[expandoId]; 5532 5533 if (createIfNecessary && !expandoStore) { 5534 element.ng339 = expandoId = jqNextId(); 5535 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 5536 } 5537 5538 return expandoStore; 5539} 5540 5541 5542function jqLiteData(element, key, value) { 5543 if (jqLiteAcceptsData(element)) { 5544 5545 var isSimpleSetter = isDefined(value); 5546 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 5547 var massGetter = !key; 5548 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 5549 var data = expandoStore && expandoStore.data; 5550 5551 if (isSimpleSetter) { // data('key', value) 5552 data[key] = value; 5553 } else { 5554 if (massGetter) { // data() 5555 return data; 5556 } else { 5557 if (isSimpleGetter) { // data('key') 5558 // don't force creation of expandoStore if it doesn't exist yet 5559 return data && data[key]; 5560 } else { // mass-setter: data({key1: val1, key2: val2}) 5561 extend(data, key); 5562 } 5563 } 5564 } 5565 } 5566} 5567 5568function jqLiteHasClass(element, selector) { 5569 if (!element.getAttribute) return false; 5570 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 5571 indexOf(" " + selector + " ") > -1); 5572} 5573 5574function jqLiteRemoveClass(element, cssClasses) { 5575 if (cssClasses && element.setAttribute) { 5576 forEach(cssClasses.split(' '), function(cssClass) { 5577 element.setAttribute('class', trim( 5578 (" " + (element.getAttribute('class') || '') + " ") 5579 .replace(/[\n\t]/g, " ") 5580 .replace(" " + trim(cssClass) + " ", " ")) 5581 ); 5582 }); 5583 } 5584} 5585 5586function jqLiteAddClass(element, cssClasses) { 5587 if (cssClasses && element.setAttribute) { 5588 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 5589 .replace(/[\n\t]/g, " "); 5590 5591 forEach(cssClasses.split(' '), function(cssClass) { 5592 cssClass = trim(cssClass); 5593 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 5594 existingClasses += cssClass + ' '; 5595 } 5596 }); 5597 5598 element.setAttribute('class', trim(existingClasses)); 5599 } 5600} 5601 5602 5603function jqLiteAddNodes(root, elements) { 5604 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 5605 5606 if (elements) { 5607 5608 // if a Node (the most common case) 5609 if (elements.nodeType) { 5610 root[root.length++] = elements; 5611 } else { 5612 var length = elements.length; 5613 5614 // if an Array or NodeList and not a Window 5615 if (typeof length === 'number' && elements.window !== elements) { 5616 if (length) { 5617 for (var i = 0; i < length; i++) { 5618 root[root.length++] = elements[i]; 5619 } 5620 } 5621 } else { 5622 root[root.length++] = elements; 5623 } 5624 } 5625 } 5626} 5627 5628 5629function jqLiteController(element, name) { 5630 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 5631} 5632 5633function jqLiteInheritedData(element, name, value) { 5634 // if element is the document object work with the html element instead 5635 // this makes $(document).scope() possible 5636 if (element.nodeType == NODE_TYPE_DOCUMENT) { 5637 element = element.documentElement; 5638 } 5639 var names = isArray(name) ? name : [name]; 5640 5641 while (element) { 5642 for (var i = 0, ii = names.length; i < ii; i++) { 5643 if (isDefined(value = jqLite.data(element, names[i]))) return value; 5644 } 5645 5646 // If dealing with a document fragment node with a host element, and no parent, use the host 5647 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 5648 // to lookup parent controllers. 5649 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 5650 } 5651} 5652 5653function jqLiteEmpty(element) { 5654 jqLiteDealoc(element, true); 5655 while (element.firstChild) { 5656 element.removeChild(element.firstChild); 5657 } 5658} 5659 5660function jqLiteRemove(element, keepData) { 5661 if (!keepData) jqLiteDealoc(element); 5662 var parent = element.parentNode; 5663 if (parent) parent.removeChild(element); 5664} 5665 5666 5667function jqLiteDocumentLoaded(action, win) { 5668 win = win || window; 5669 if (win.document.readyState === 'complete') { 5670 // Force the action to be run async for consistent behavior 5671 // from the action's point of view 5672 // i.e. it will definitely not be in a $apply 5673 win.setTimeout(action); 5674 } else { 5675 // No need to unbind this handler as load is only ever called once 5676 jqLite(win).on('load', action); 5677 } 5678} 5679
5680////////////////////////////////////////// 5681// Functions which are declared directly. 5682////////////////////////////////////////// 5683var JQLitePrototype = JQLite.prototype = { 5684 ready: function(fn) { 5685 var fired = false; 5686 5687 function trigger() { 5688 if (fired) return; 5689 fired = true; 5690 fn(); 5691 } 5692 5693 // check if document is already loaded 5694 if (window.document.readyState === 'complete') { 5695 window.setTimeout(trigger); 5696 } else { 5697 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 5698 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 5699 // jshint -W064 5700 JQLite(window).on('load', trigger); // fallback to window.onload for others 5701 // jshint +W064 5702 } 5703 }, 5704 toString: function() { 5705 var value = []; 5706 forEach(this, function(e) { value.push('' + e);}); 5707 return '[' + value.join(', ') + ']'; 5708 }, 5709 5710 eq: function(index) { 5711 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 5712 }, 5713 5714 length: 0, 5715 push: push, 5716 sort: [].sort, 5717 splice: [].splice 5718}; 5719 5720////////////////////////////////////////// 5721// Functions iterating getter/setters. 5722// these functions return self on setter and 5723// value on get. 5724////////////////////////////////////////// 5725var BOOLEAN_ATTR = {}; 5726forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 5727 BOOLEAN_ATTR[lowercase(value)] = value; 5728}); 5729var BOOLEAN_ELEMENTS = {}; 5730forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 5731 BOOLEAN_ELEMENTS[value] = true; 5732}); 5733var ALIASED_ATTR = { 5734 'ngMinlength': 'minlength', 5735 'ngMaxlength': 'maxlength', 5736 'ngMin': 'min', 5737 'ngMax': 'max', 5738 'ngPattern': 'pattern' 5739}; 5740 5741function getBooleanAttrName(element, name) { 5742 // check dom last since we will most likely fail on name 5743 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 5744 5745 // booleanAttr is here twice to minimize DOM access 5746 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 5747} 5748 5749function getAliasedAttrName(name) { 5750 return ALIASED_ATTR[name]; 5751} 5752 5753forEach({ 5754 data: jqLiteData, 5755 removeData: jqLiteRemoveData, 5756 hasData: jqLiteHasData, 5757 cleanData: jqLiteCleanData 5758}, function(fn, name) { 5759 JQLite[name] = fn; 5760}); 5761 5762forEach({ 5763 data: jqLiteData, 5764 inheritedData: jqLiteInheritedData, 5765 5766 scope: function(element) { 5767 // Can't use jqLiteData here directly so we stay compatible with jQuery! 5768 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 5769 }, 5770 5771 isolateScope: function(element) { 5772 // Can't use jqLiteData here directly so we stay compatible with jQuery! 5773 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 5774 }, 5775 5776 controller: jqLiteController, 5777 5778 injector: function(element) { 5779 return jqLiteInheritedData(element, '$injector'); 5780 }, 5781 5782 removeAttr: function(element, name) { 5783 element.removeAttribute(name); 5784 }, 5785 5786 hasClass: jqLiteHasClass, 5787 5788 css: function(element, name, value) { 5789 name = camelCase(name); 5790 5791 if (isDefined(value)) { 5792 element.style[name] = value; 5793 } else { 5794 return element.style[name]; 5795 } 5796 }, 5797 5798 attr: function(element, name, value) { 5799 var nodeType = element.nodeType; 5800 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 5801 return; 5802 } 5803 var lowercasedName = lowercase(name); 5804 if (BOOLEAN_ATTR[lowercasedName]) { 5805 if (isDefined(value)) { 5806 if (!!value) { 5807 element[name] = true; 5808 element.setAttribute(name, lowercasedName); 5809 } else { 5810 element[name] = false; 5811 element.removeAttribute(lowercasedName); 5812 } 5813 } else { 5814 return (element[name] || 5815 (element.attributes.getNamedItem(name) || noop).specified) 5816 ? lowercasedName 5817 : undefined; 5818 } 5819 } else if (isDefined(value)) { 5820 element.setAttribute(name, value); 5821 } else if (element.getAttribute) { 5822 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 5823 // some elements (e.g. Document) don't have get attribute, so return undefined 5824 var ret = element.getAttribute(name, 2); 5825 // normalize non-existing attributes to undefined (as jQuery) 5826 return ret === null ? undefined : ret; 5827 } 5828 }, 5829 5830 prop: function(element, name, value) { 5831 if (isDefined(value)) { 5832 element[name] = value; 5833 } else { 5834 return element[name]; 5835 } 5836 }, 5837 5838 text: (function() { 5839 getText.$dv = ''; 5840 return getText; 5841 5842 function getText(element, value) { 5843 if (isUndefined(value)) { 5844 var nodeType = element.nodeType; 5845 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 5846 } 5847 element.textContent = value; 5848 } 5849 })(), 5850 5851 val: function(element, value) { 5852 if (isUndefined(value)) { 5853 if (element.multiple && nodeName_(element) === 'select') { 5854 var result = [];
vendor: 10,249 bytes, lines 5855-6194
5855 forEach(element.options, function(option) { 5856 if (option.selected) { 5857 result.push(option.value || option.text); 5858 } 5859 }); 5860 return result.length === 0 ? null : result; 5861 } 5862 return element.value; 5863 } 5864 element.value = value; 5865 }, 5866 5867 html: function(element, value) { 5868 if (isUndefined(value)) { 5869 return element.innerHTML; 5870 } 5871 jqLiteDealoc(element, true); 5872 element.innerHTML = value; 5873 }, 5874 5875 empty: jqLiteEmpty 5876}, function(fn, name) { 5877 /** 5878 * Properties: writes return selection, reads return first value 5879 */ 5880 JQLite.prototype[name] = function(arg1, arg2) { 5881 var i, key; 5882 var nodeCount = this.length; 5883 5884 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 5885 // in a way that survives minification. 5886 // jqLiteEmpty takes no arguments but is a setter. 5887 if (fn !== jqLiteEmpty && 5888 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 5889 if (isObject(arg1)) { 5890 5891 // we are a write, but the object properties are the key/values 5892 for (i = 0; i < nodeCount; i++) { 5893 if (fn === jqLiteData) { 5894 // data() takes the whole object in jQuery 5895 fn(this[i], arg1); 5896 } else { 5897 for (key in arg1) { 5898 fn(this[i], key, arg1[key]); 5899 } 5900 } 5901 } 5902 // return self for chaining 5903 return this; 5904 } else { 5905 // we are a read, so read the first child. 5906 // TODO: do we still need this? 5907 var value = fn.$dv; 5908 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 5909 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 5910 for (var j = 0; j < jj; j++) { 5911 var nodeValue = fn(this[j], arg1, arg2); 5912 value = value ? value + nodeValue : nodeValue; 5913 } 5914 return value; 5915 } 5916 } else { 5917 // we are a write, so apply to all children 5918 for (i = 0; i < nodeCount; i++) { 5919 fn(this[i], arg1, arg2); 5920 } 5921 // return self for chaining 5922 return this; 5923 } 5924 }; 5925}); 5926 5927function createEventHandler(element, events) { 5928 var eventHandler = function(event, type) { 5929 // jQuery specific api 5930 event.isDefaultPrevented = function() { 5931 return event.defaultPrevented; 5932 }; 5933 5934 var eventFns = events[type || event.type]; 5935 var eventFnsLength = eventFns ? eventFns.length : 0; 5936 5937 if (!eventFnsLength) return; 5938 5939 if (isUndefined(event.immediatePropagationStopped)) { 5940 var originalStopImmediatePropagation = event.stopImmediatePropagation; 5941 event.stopImmediatePropagation = function() { 5942 event.immediatePropagationStopped = true; 5943 5944 if (event.stopPropagation) { 5945 event.stopPropagation(); 5946 } 5947 5948 if (originalStopImmediatePropagation) { 5949 originalStopImmediatePropagation.call(event); 5950 } 5951 }; 5952 } 5953 5954 event.isImmediatePropagationStopped = function() { 5955 return event.immediatePropagationStopped === true; 5956 }; 5957 5958 // Some events have special handlers that wrap the real handler 5959 var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; 5960 5961 // Copy event handlers in case event handlers array is modified during execution. 5962 if ((eventFnsLength > 1)) { 5963 eventFns = shallowCopy(eventFns); 5964 } 5965 5966 for (var i = 0; i < eventFnsLength; i++) { 5967 if (!event.isImmediatePropagationStopped()) { 5968 handlerWrapper(element, event, eventFns[i]); 5969 } 5970 } 5971 }; 5972 5973 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 5974 // events on `element` 5975 eventHandler.elem = element; 5976 return eventHandler; 5977} 5978 5979function defaultHandlerWrapper(element, event, handler) { 5980 handler.call(element, event); 5981} 5982 5983function specialMouseHandlerWrapper(target, event, handler) { 5984 // Refer to jQuery's implementation of mouseenter & mouseleave 5985 // Read about mouseenter and mouseleave: 5986 // http://www.quirksmode.org/js/events_mouse.html#link8 5987 var related = event.relatedTarget; 5988 // For mousenter/leave call the handler if related is outside the target. 5989 // NB: No relatedTarget if the mouse left/entered the browser window 5990 if (!related || (related !== target && !jqLiteContains.call(target, related))) { 5991 handler.call(target, event); 5992 } 5993} 5994 5995////////////////////////////////////////// 5996// Functions iterating traversal. 5997// These functions chain results into a single 5998// selector. 5999////////////////////////////////////////// 6000forEach({ 6001 removeData: jqLiteRemoveData, 6002 6003 on: function jqLiteOn(element, type, fn, unsupported) { 6004 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 6005 6006 // Do not add event handlers to non-elements because they will not be cleaned up. 6007 if (!jqLiteAcceptsData(element)) { 6008 return; 6009 } 6010 6011 var expandoStore = jqLiteExpandoStore(element, true); 6012 var events = expandoStore.events; 6013 var handle = expandoStore.handle; 6014 6015 if (!handle) { 6016 handle = expandoStore.handle = createEventHandler(element, events); 6017 } 6018 6019 // http://jsperf.com/string-indexof-vs-split 6020 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 6021 var i = types.length; 6022 6023 var addHandler = function(type, specialHandlerWrapper, noEventListener) { 6024 var eventFns = events[type]; 6025 6026 if (!eventFns) { 6027 eventFns = events[type] = []; 6028 eventFns.specialHandlerWrapper = specialHandlerWrapper; 6029 if (type !== '$destroy' && !noEventListener) { 6030 addEventListenerFn(element, type, handle); 6031 } 6032 } 6033 6034 eventFns.push(fn); 6035 }; 6036 6037 while (i--) { 6038 type = types[i]; 6039 if (MOUSE_EVENT_MAP[type]) { 6040 addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); 6041 addHandler(type, undefined, true); 6042 } else { 6043 addHandler(type); 6044 } 6045 } 6046 }, 6047 6048 off: jqLiteOff, 6049 6050 one: function(element, type, fn) { 6051 element = jqLite(element); 6052 6053 //add the listener twice so that when it is called 6054 //you can remove the original function and still be 6055 //able to call element.off(ev, fn) normally 6056 element.on(type, function onFn() { 6057 element.off(type, fn); 6058 element.off(type, onFn); 6059 }); 6060 element.on(type, fn); 6061 }, 6062 6063 replaceWith: function(element, replaceNode) { 6064 var index, parent = element.parentNode; 6065 jqLiteDealoc(element); 6066 forEach(new JQLite(replaceNode), function(node) { 6067 if (index) { 6068 parent.insertBefore(node, index.nextSibling); 6069 } else { 6070 parent.replaceChild(node, element); 6071 } 6072 index = node; 6073 }); 6074 }, 6075 6076 children: function(element) { 6077 var children = []; 6078 forEach(element.childNodes, function(element) { 6079 if (element.nodeType === NODE_TYPE_ELEMENT) { 6080 children.push(element); 6081 } 6082 }); 6083 return children; 6084 }, 6085 6086 contents: function(element) { 6087 return element.contentDocument || element.childNodes || []; 6088 }, 6089 6090 append: function(element, node) { 6091 var nodeType = element.nodeType; 6092 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 6093 6094 node = new JQLite(node); 6095 6096 for (var i = 0, ii = node.length; i < ii; i++) { 6097 var child = node[i]; 6098 element.appendChild(child); 6099 } 6100 }, 6101 6102 prepend: function(element, node) { 6103 if (element.nodeType === NODE_TYPE_ELEMENT) { 6104 var index = element.firstChild; 6105 forEach(new JQLite(node), function(child) { 6106 element.insertBefore(child, index); 6107 }); 6108 } 6109 }, 6110 6111 wrap: function(element, wrapNode) { 6112 jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); 6113 }, 6114 6115 remove: jqLiteRemove, 6116 6117 detach: function(element) { 6118 jqLiteRemove(element, true); 6119 }, 6120 6121 after: function(element, newElement) { 6122 var index = element, parent = element.parentNode; 6123 newElement = new JQLite(newElement); 6124 6125 for (var i = 0, ii = newElement.length; i < ii; i++) { 6126 var node = newElement[i]; 6127 parent.insertBefore(node, index.nextSibling); 6128 index = node; 6129 } 6130 }, 6131 6132 addClass: jqLiteAddClass, 6133 removeClass: jqLiteRemoveClass, 6134 6135 toggleClass: function(element, selector, condition) { 6136 if (selector) { 6137 forEach(selector.split(' '), function(className) { 6138 var classCondition = condition; 6139 if (isUndefined(classCondition)) { 6140 classCondition = !jqLiteHasClass(element, className); 6141 } 6142 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 6143 }); 6144 } 6145 }, 6146 6147 parent: function(element) { 6148 var parent = element.parentNode; 6149 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 6150 }, 6151 6152 next: function(element) { 6153 return element.nextElementSibling; 6154 }, 6155 6156 find: function(element, selector) { 6157 if (element.getElementsByTagName) { 6158 return element.getElementsByTagName(selector); 6159 } else { 6160 return []; 6161 } 6162 }, 6163 6164 clone: jqLiteClone, 6165 6166 triggerHandler: function(element, event, extraParameters) { 6167 6168 var dummyEvent, eventFnsCopy, handlerArgs; 6169 var eventName = event.type || event; 6170 var expandoStore = jqLiteExpandoStore(element); 6171 var events = expandoStore && expandoStore.events; 6172 var eventFns = events && events[eventName]; 6173 6174 if (eventFns) { 6175 // Create a dummy event to pass to the handlers 6176 dummyEvent = { 6177 preventDefault: function() { this.defaultPrevented = true; }, 6178 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 6179 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 6180 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 6181 stopPropagation: noop, 6182 type: eventName, 6183 target: element 6184 }; 6185 6186 // If a custom event was provided then extend our dummy event with it 6187 if (event.type) { 6188 dummyEvent = extend(dummyEvent, event); 6189 } 6190 6191 // Copy event handlers in case event handlers array is modified during execution. 6192 eventFnsCopy = shallowCopy(eventFns); 6193 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 6194
6195 forEach(eventFnsCopy, function(fn) { 6196 if (!dummyEvent.isImmediatePropagationStopped()) { 6197 fn.apply(element, handlerArgs); 6198 } 6199 }); 6200 } 6201 } 6202}, function(fn, name) { 6203 /** 6204 * chaining functions 6205 */ 6206 JQLite.prototype[name] = function(arg1, arg2, arg3) { 6207 var value; 6208 6209 for (var i = 0, ii = this.length; i < ii; i++) { 6210 if (isUndefined(value)) { 6211 value = fn(this[i], arg1, arg2, arg3); 6212 if (isDefined(value)) { 6213 // any function which returns a value needs to be wrapped 6214 value = jqLite(value); 6215 } 6216 } else { 6217 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 6218 } 6219 } 6220 return isDefined(value) ? value : this; 6221 }; 6222 6223 // bind legacy bind/unbind to on/off 6224 JQLite.prototype.bind = JQLite.prototype.on; 6225 JQLite.prototype.unbind = JQLite.prototype.off; 6226}); 6227 6228 6229// Provider for private $$jqLite service 6230function $$jqLiteProvider() { 6231 this.$get = function $$jqLite() { 6232 return extend(JQLite, { 6233 hasClass: function(node, classes) { 6234 if (node.attr) node = node[0]; 6235 return jqLiteHasClass(node, classes); 6236 }, 6237 addClass: function(node, classes) { 6238 if (node.attr) node = node[0]; 6239 return jqLiteAddClass(node, classes); 6240 }, 6241 removeClass: function(node, classes) { 6242 if (node.attr) node = node[0]; 6243 return jqLiteRemoveClass(node, classes); 6244 } 6245 }); 6246 }; 6247} 6248 6249/** 6250 * Computes a hash of an 'obj'. 6251 * Hash of a: 6252 * string is string 6253 * number is number as string 6254 * object is either result of calling $$hashKey function on the object or uniquely generated id, 6255 * that is also assigned to the $$hashKey property of the object. 6256 * 6257 * @param obj 6258 * @returns {string} hash string such that the same input will have the same hash string. 6259 * The resulting string key is in 'type:hashKey' format. 6260 */ 6261function hashKey(obj, nextUidFn) { 6262 var key = obj && obj.$$hashKey; 6263 6264 if (key) { 6265 if (typeof key === 'function') { 6266 key = obj.$$hashKey(); 6267 } 6268 return key; 6269 } 6270 6271 var objType = typeof obj; 6272 if (objType == 'function' || (objType == 'object' && obj !== null)) { 6273 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 6274 } else { 6275 key = objType + ':' + obj; 6276 } 6277 6278 return key; 6279} 6280 6281/** 6282 * HashMap which can use objects as keys 6283 */ 6284function HashMap(array, isolatedUid) { 6285 if (isolatedUid) { 6286 var uid = 0; 6287 this.nextUid = function() { 6288 return ++uid; 6289 }; 6290 } 6291 forEach(array, this.put, this); 6292} 6293HashMap.prototype = { 6294 /** 6295 * Store key value pair 6296 * @param key key to store can be any type 6297 * @param value value to store can be any type 6298 */ 6299 put: function(key, value) { 6300 this[hashKey(key, this.nextUid)] = value; 6301 }, 6302 6303 /** 6304 * @param key 6305 * @returns {Object} the value for the key 6306 */ 6307 get: function(key) { 6308 return this[hashKey(key, this.nextUid)]; 6309 }, 6310 6311 /** 6312 * Remove the key/value pair 6313 * @param key 6314 */ 6315 remove: function(key) { 6316 var value = this[key = hashKey(key, this.nextUid)]; 6317 delete this[key]; 6318 return value; 6319 } 6320}; 6321 6322var $$HashMapProvider = [function() { 6323 this.$get = [function() { 6324 return HashMap; 6325 }]; 6326}]; 6327 6328/** 6329 * @ngdoc function 6330 * @module ng 6331 * @name angular.injector 6332 * @kind function 6333 * 6334 * @description 6335 * Creates an injector object that can be used for retrieving services as well as for 6336 * dependency injection (see {@link guide/di dependency injection}). 6337 * 6338 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 6339 * {@link angular.module}. The `ng` module must be explicitly added. 6340 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 6341 * disallows argument name annotation inference. 6342 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 6343 * 6344 * @example 6345 * Typical usage 6346 * ```js 6347 * // create an injector 6348 * var $injector = angular.injector(['ng']); 6349 * 6350 * // use the injector to kick off your application 6351 * // use the type inference to auto inject arguments, or use impl
6351icit injection 6352 * $injector.invoke(function($rootScope, $compile, $document) { 6353 * $compile($document)($rootScope); 6354 * $rootScope.$digest(); 6355 * }); 6356 * ``` 6357 * 6358 * Sometimes you want to get access to the injector of a currently running Angular app 6359 * from outside Angular. Perhaps, you want to inject and compile some markup after the 6360 * application has been bootstrapped. You can do this using the extra `injector()` added 6361 * to JQuery/jqLite elements. See {@link angular.element}. 6362 * 6363 * *This is fairly rare but could be the case if a third party library is injecting the 6364 * markup.* 6365 * 6366 * In the following example a new block of HTML containing a `ng-controller` 6367 * directive is added to the end of the document body by JQuery. We then compile and link 6368 * it into the current AngularJS scope. 6369 * 6370 * ```js 6371 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 6372 * $(document.body).append($div); 6373 * 6374 * angular.element(document).injector().invoke(function($compile) { 6375 * var scope = angular.element($div).scope(); 6376 * $compile($div)(scope); 6377 * }); 6378 * ``` 6379 */ 6380 6381 6382/** 6383 * @ngdoc module 6384 * @name auto 6385 * @installation 6386 * @description 6387 * 6388 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 6389 */ 6390 6391var ARROW_ARG = /^([^\(]+?)=>/; 6392var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; 6393var FN_ARG_SPLIT = /,/; 6394var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 6395var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 6396var $injectorMinErr = minErr('$injector'); 6397 6398function extractArgs(fn) { 6399 var fnText = Function.prototype.toString.call(fn).replace(STRIP_COMMENTS, ''), 6400 args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); 6401 return args; 6402} 6403 6404function anonFn(fn) { 6405 // For anonymous functions, showing at the very least the function signature can help in 6406 // debugging. 6407 var args = extractArgs(fn); 6408 if (args) { 6409 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 6410 } 6411 return 'fn'; 6412} 6413 6414function annotate(fn, strictDi, name) { 6415 var $inject, 6416 argDecl, 6417 last; 6418 6419 if (typeof fn === 'function') { 6420 if (!($inject = fn.$inject)) { 6421 $inject = []; 6422 if (fn.length) { 6423 if (strictDi) { 6424 if (!isString(name) || !name) { 6425 name = fn.name || anonFn(fn); 6426 } 6427 throw $injectorMinErr('strictdi', 6428 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 6429 } 6430 argDecl = extractArgs(fn); 6431 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 6432 arg.replace(FN_ARG, function(all, underscore, name) { 6433 $inject.push(name); 6434 }); 6435 }); 6436 } 6437 fn.$inject = $inject; 6438 } 6439 } else if (isArray(fn)) { 6440 last = fn.length - 1; 6441 assertArgFn(fn[last], 'fn'); 6442 $inject = fn.slice(0, last); 6443 } else { 6444 assertArgFn(fn, 'fn', true); 6445 } 6446 return $inject; 6447} 6448 6449/////////////////////////////////////// 6450 6451/** 6452 * @ngdoc service 6453 * @name $injector 6454 * 6455 * @description 6456 * 6457 * `$injector` is used to retrieve object instances as defined by 6458 * {@link auto.$provide provider}, instantiate types, invoke methods, 6459 * and load modules. 6460 * 6461 * The following always holds true: 6462 * 6463 * ```js 6464 * var $injector = angular.injector(); 6465 * expect($injector.get('$injector')).toBe($injector); 6466 * expect($injector.invoke(function($injector) { 6467 * return $injector; 6468 * })).toBe($injector); 6469 * ``` 6470 * 6471 * # Injection Function Annotation 6472 * 6473 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 6474 * following are all valid ways of annotating function with injection arguments and are equivalent. 6475 * 6476 * ```js 6477 * // inferred (only works if code not minified/obfuscated) 6478 * $injector.invoke(function(serviceA){}); 6479 * 6480 * // annotated 6481 * function explicit(serviceA) {}; 6482 * explicit.$inject = ['serviceA']; 6483 * $injector.invoke(explicit); 6484 * 6485 * // inline 6486 * $injector.invoke(['serviceA', function(serviceA){}]); 6487 * ``` 6488 * 6489 * ## Inference 6490 * 6491 * In JavaScript calling `toString()` on a function returns the function definition. The definition 6492 * can then be parsed and the function arguments can be extracted. This method of discovering 6493 * annotations is disallowed when the injector is in strict mode. 6494 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 6495 * argument names. 6496 * 6497 * ## `$inject` Annotation 6498 * By adding an `$inject` property onto a function the injection parameters can be specified. 6499 * 6500 * ## Inline 6501 * As an array of injection names, where the last item in the array is the function to call. 6502 */ 6503 6504/** 6505 * @ngdoc method 6506 * @name $injector#get 6507 * 6508 * @description 6509 * Return an instance of the service. 6510 * 6511 * @param {string} name The name of the instance to retrieve. 6512 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 6513 * @return {*} The instance. 6514 */ 6515 6516/** 6517 * @ngdoc method 6518 * @name $injector#invoke 6519 * 6520 * @description 6521 * Invoke the method and supply the method arguments from the `$injector`. 6522 * 6523 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 6524 * injected according to the {@link guide/di $inject Annotation} rules. 6525 * @param {Object=} self The `this` for the invoked method. 6526 * @param {Object=} locals Optional object. If preset then any argument names are read from this 6527 * object first, before the `$injector` is consulted. 6528 * @returns {*} the value returned by the invoked `fn` function. 6529 */ 6530 6531/** 6532 * @ngdoc method 6533 * @name $injector#has 6534 * 6535 * @description 6536 * Allows the user to query if the particular service exists. 6537 * 6538 * @param {string} name Name of the service to query. 6539 * @returns {boolean} `true` if injector has given service. 6540 */ 6541 6542/** 6543 * @ngdoc method 6544 * @name $injector#instantiate 6545 * @description 6546 * Create a new instance of JS type. The method takes a constructor function, invokes the new 6547 * operator, and supplies all of the arguments to the constructor function as specified by the 6548 * constructor annotation. 6549 * 6550 * @param {Function} Type Annotated constructor function. 6551 * @param {Object=} locals Optional object. If preset then any argument names are read from this 6552 * object first, before the `$injector` is consulted. 6553 * @returns {Object} new instance of `Type`. 6554 */ 6555 6556/** 6557 * @ngdoc method 6558 * @name $injector#annotate 6559 * 6560 * @description 6561 * Returns an array of service names which the function is requesting for injection. This API is 6562 * used by the injector to determine which services need to be injected into the function when the 6563 * function is invoked. There are three ways in which the function can be annotated with the needed 6564 * dependencies. 6565 * 6566 * # Argument names 6567 * 6568 * The simplest form is to extract the dependencies from the arguments of the function. This is done
6569 * by converting the function into a string using `toString()` method and extracting the argument 6570 * names. 6571 * ```js 6572 * // Given 6573 * function MyController($scope, $route) { 6574 * // ... 6575 * } 6576 * 6577 * // Then 6578 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 6579 * ``` 6580 * 6581 * You can disallow this method by using strict injection mode. 6582 * 6583 * This method does not work with code minification / obfuscation. For this reason the following 6584 * annotation strategies are supported. 6585 * 6586 * # The `$inject` property 6587 * 6588 * If a function has an `$inject` property and its value is an array of strings, then the strings 6589 * represent names of services to be injected into the function. 6590 * ```js 6591 * // Given 6592 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 6593 * // ... 6594 * } 6595 * // Define function dependencies 6596 * MyController['$inject'] = ['$scope', '$route']; 6597 * 6598 * // Then 6599 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 6600 * ``` 6601 * 6602 * # The array notation 6603 * 6604 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 6605 * is very inconvenient. In these situations using the array notation to specify the dependencies in 6606 * a way that survives minification is a better choice: 6607 * 6608 * ```js 6609 * // We wish to write this (not minification / obfuscation safe) 6610 * injector.invoke(function($compile, $rootScope) { 6611 * // ... 6612 * }); 6613 * 6614 * // We are forced to write break inlining 6615 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 6616 * // ... 6617 * }; 6618 * tmpFn.$inject = ['$compile', '$rootScope']; 6619 * injector.invoke(tmpFn); 6620 * 6621 * // To better support inline function the inline annotation is supported 6622 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 6623 * // ... 6624 * }]); 6625 * 6626 * // Therefore 6627 * expect(injector.annotate( 6628 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 6629 * ).toEqual(['$compile', '$rootScope']); 6630 * ``` 6631 * 6632 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 6633 * be retrieved as described above. 6634 * 6635 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 6636 * 6637 * @returns {Array.<string>} The names of the services which the function requires. 6638 */ 6639 6640 6641 6642 6643/** 6644 * @ngdoc service 6645 * @name $provide 6646 * 6647 * @description 6648 * 6649 * The {@link auto.$provide $provide} service has a number of methods for registering components 6650 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 6651 * {@link angular.Module}. 6652 * 6653 * An Angular **service** is a singleton object created by a **service factory**. These **service 6654 * factories** are functions which, in turn, are created by a **service provider**. 6655 * The **service providers** are constructor functions. When instantiated they must contain a 6656 * property called `$get`, which holds the **service factory** function. 6657 * 6658 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 6659 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 6660 * function to get the instance of the **service**. 6661 * 6662 * Often services have no configuration options and there is no need to add methods to the service 6663 * provider. The provider will be no more than a constructor function with a `$get` property. For 6664 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 6665 * services without specifying a provider. 6666 * 6667 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 6668 * {@link auto.$injector $injector} 6669 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 6670 * providers and services. 6671 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 6672 * services, not providers. 6673 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 6674 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 6675 * given factory function. 6676 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 6677 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 6678 * a new object using the given constructor function. 6679 * 6680 * See the individual methods for more information and examples. 6681 */ 6682 6683/** 6684 * @ngdoc method 6685 * @name $provide#provider 6686 * @description 6687 * 6688 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 6689 * are constructor functions, whose instances are responsible for "providing" a factory for a 6690 * service. 6691 * 6692 * Service provider names start with the name of the service they provide followed by `Provider`. 6693 * For example, the {@link ng.$log $log} service has a provider called 6694 * {@link ng.$logProvider $logProvider}. 6695 * 6696 * Service provider objects can have additional methods which allow configuration of the provider 6697 * and its service. Importantly, you can configure what kind of service is created by the `$get` 6698 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 6699 * method {@link ng.$logProvider#debugEnabled debugEnabled} 6700 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 6701 * console or not. 6702 * 6703 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 6704 'Provider'` key. 6705 * @param {(Object|function())} provider If the provider is: 6706 * 6707 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 6708 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 6709 * - `Constructor`: a new instance of the provider will be created using 6710 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 6711 * 6712 * @returns {Object} registered provider instance 6713 6714 * @example 6715 * 6716 * The following example shows how to create a simple event tracking service and register it using 6717 * {@link auto.$provide#provider $provide.provider()}. 6718 * 6719 * ```js 6720 * // Define the eventTracker provider 6721 * function EventTrackerProvider() { 6722 * var trackingUrl = '/track'; 6723 * 6724 * // A provider method for configuring where the tracked events should been saved 6725 * this.setTrackingUrl = function(url) { 6726 * trackingUrl = url; 6727 * }; 6728 * 6729 * // The service factory function 6730 * this.$get = ['$http', function($http) { 6731 * var trackedEvents = {}; 6732 * return { 6733 * // Call this to track an event 6734 * event: function(event) { 6735 * var count = trackedEvents[event] || 0; 6736 * count += 1; 6737 * trackedEvents[event] = count; 6738 * return count; 6739 * }, 6740 * // Call this to save the tracked events to the trackingUrl 6741 * save: function() { 6742 * $http.post(trackingUrl, trackedEvents); 6743 * } 6744 * }; 6745 * }]; 6746 * } 6747 * 6748 * describe('eventTracker', function() { 6749 * var postSpy; 6750 * 6751 * beforeEach(module(function($provide) { 6752 * // Register the eventTracker provider 6753 * $provide.provider('eventTracker', EventTrackerProvider); 6754 * })); 6755 * 6756 * beforeEach(module(function(eventTrackerProvider) { 6757 * // Configure eventTracker provider 6758 * eventTrackerProvider.setTrackingUrl('/custom-track'); 6759 * })); 6760 * 6761 * it('tracks events', inject(function(eventTracker) { 6762 * expect(eventTracker.event('login')).toEqual(1); 6763 * expect(eventTracker.event('login')).toEqual(2); 6764 * })); 6765 * 6766 * it('saves to the tracking url', inject(function(eventTracker, $http) { 6767 * postSpy = spyOn($http, 'post'); 6768 * eventTracker.event('login'); 6769 * eventTracker.save(); 6770 * expect(postSpy).toHaveBeenCalled(); 6771 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 6772 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 6773 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 6774 * })); 6775 * }); 6776 * ``` 6777 */ 6778 6779/** 6780 * @ngdoc method 6781 * @name $provide#factory 6782 * @description 6783 * 6784 * Register a **service factory**, which will be called to return the service instance. 6785 * This is short for registering a service where its provider consists of only a `$get` property, 6786 * which is the given service factory function. 6787 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 6788 * configure your service in a provider. 6789 * 6790 * @param {string} name The name of the instance. 6791 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 6792 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 6793 * @returns {Object} registered provider instance 6794 * 6795 * @example 6796 * Here is an example of registering a service 6797 * ```js 6798 * $provide.factory('ping', ['$http', function($http) { 6799 * return function ping() { 6800 * return $http.send('/ping'); 6801 * }; 6802 * }]); 6803 * ``` 6804 * You would then inject and use this service like this: 6805 * ```js 6806 * someModule.controller('Ctrl', ['ping', function(ping) { 6807 * ping(); 6808 * }]); 6809 * ``` 6810 */ 6811 6812 6813/** 6814 * @ngdoc method 6815 * @name $provide#service 6816 * @description 6817 * 6818 * Register a **service constructor**, which will be invoked with `new` to create the service 6819 * instance. 6820 * This is short for registering a service where its provider's `$get` property is a factory 6821 * function that returns an instance instantiated by the injector from the service constructor 6822 * function. 6823 * 6824 * Internally it looks a bit like this: 6825 * 6826 * ``` 6827 * { 6828 * $get: function() { 6829 * return $injector.instantiate(constructor); 6830 * } 6831 * } 6832 * ``` 6833 * 6834 * 6835 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 6836 * as a type/class. 6837 * 6838 * @param {string} name The name of the instance. 6839 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 6840 * that will be instantiated. 6841 * @returns {Object} registered provider instance 6842 * 6843 * @example 6844 * Here is an example of registering a service using 6845 * {@link auto.$provide#service $provide.service(class)}. 6846 * ```js 6847 * var Ping = function($http) { 6848 * this.$http = $http; 6849 * }; 6850 * 6851 * Ping.$inject = ['$http']; 6852 * 6853 * Ping.prototype.send = function() { 6854 * return this.$http.get('/ping'); 6855 * }; 6856 * $provide.service('ping', Ping); 6857 * ``` 6858 * You would then inject and use this service like this: 6859 * ```js 6860 * someModule.controller('Ctrl', ['ping', function(ping) { 6861 * ping.send(); 6862 * }]); 6863 * ``` 6864 */ 6865 6866 6867/** 6868 * @ngdoc method 6869 * @name $provide#value 6870 * @description 6871 * 6872 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 6873 * number, an array, an object or a function. This is short for registering a service where its 6874 * provider's `$get` property is a factory function that takes no arguments and returns the **value 6875 * service**. That also means it is not possible to inject other services into a value service. 6876 * 6877 * Value services are similar to constant services, except that they cannot be injected into a 6878 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 6879 * an Angular {@link auto.$provide#decorator decorator}. 6880 * 6881 * @param {string} name The name of the instance. 6882 * @param {*} value The value. 6883 * @returns {Object} registered provider instance 6884 * 6885 * @example 6886 * Here are some examples of creating value services. 6887 * ```js 6888 * $provide.value('ADMIN_USER', 'admin'); 6889 *
6890 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 6891 * 6892 * $provide.value('halfOf', function(value) { 6893 * return value / 2; 6894 * }); 6895 * ``` 6896 */ 6897 6898 6899/** 6900 * @ngdoc method 6901 * @name $provide#constant 6902 * @description 6903 * 6904 * Register a **constant service** with the {@link auto.$injector $injector}, such as a string, 6905 * a number, an array, an object or a function. Like the {@link auto.$provide#value value}, it is not 6906 * possible to inject other services into a constant. 6907 * 6908 * But unlike {@link auto.$provide#value value}, a constant can be 6909 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 6910 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 6911 * 6912 * @param {string} name The name of the constant. 6913 * @param {*} value The constant value. 6914 * @returns {Object} registered instance 6915 * 6916 * @example 6917 * Here a some examples of creating constants: 6918 * ```js 6919 * $provide.constant('SHARD_HEIGHT', 306); 6920 * 6921 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 6922 * 6923 * $provide.constant('double', function(value) { 6924 * return value * 2; 6925 * }); 6926 * ``` 6927 */ 6928 6929 6930/** 6931 * @ngdoc method 6932 * @name $provide#decorator 6933 * @description 6934 * 6935 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 6936 * intercepts the creation of a service, allowing it to override or modify the behavior of the 6937 * service. The object returned by the decorator may be the original service, or a new service 6938 * object which replaces or wraps and delegates to the original service. 6939 * 6940 * @param {string} name The name of the service to decorate. 6941 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 6942 * instantiated and should return the decorated service instance. The function is called using 6943 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 6944 * Local injection arguments: 6945 * 6946 * * `$delegate` - The original service instance, which can be monkey patched, configured, 6947 * decorated or delegated to. 6948 * 6949 * @example 6950 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 6951 * calls to {@link ng.$log#error $log.warn()}. 6952 * ```js 6953 * $provide.decorator('$log', ['$delegate', function($delegate) { 6954 * $delegate.warn = $delegate.error; 6955 * return $delegate; 6956 * }]); 6957 * ``` 6958 */ 6959 6960 6961function createInjector(modulesToLoad, strictDi) { 6962 strictDi = (strictDi === true); 6963 var INSTANTIATING = {}, 6964 providerSuffix = 'Provider', 6965 path = [], 6966 loadedModules = new HashMap([], true), 6967 providerCache = { 6968 $provide: { 6969 provider: supportObject(provider), 6970 factory: supportObject(factory), 6971 service: supportObject(service), 6972 value: supportObject(value), 6973 constant: supportObject(constant), 6974 decorator: decorator 6975 } 6976 }, 6977 providerInjector = (providerCache.$injector = 6978 createInternalInjector(providerCache, function(serviceName, caller) { 6979 if (angular.isString(caller)) { 6980 path.push(caller); 6981 } 6982 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 6983 })), 6984 instanceCache = {}, 6985 protoInstanceInjector = 6986 createInternalInjector(instanceCache, function(serviceName, caller) { 6987 var provider = providerInjector.get(serviceName + providerSuffix, caller); 6988 return instanceInjector.invoke( 6989 provider.$get, provider, undefined, serviceName); 6990 }), 6991 instanceInjector = protoInstanceInjector; 6992 6993 providerCache['$injector' + providerSuffix] = { $get: valueFn(protoInstanceInjector) }; 6994 var runBlocks = loadModules(modulesToLoad); 6995 instanceInjector = protoInstanceInjector.get('$injector'); 6996 instanceInjector.strictDi = strictDi; 6997 forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); }); 6998 6999 return instanceInjector; 7000 7001 //////////////////////////////////// 7002 // $provider 7003 //////////////////////////////////// 7004 7005 function supportObject(delegate) { 7006 return function(key, value) { 7007 if (isObject(key)) {
vendor: 10,003 bytes, lines 7008-7286
7008 forEach(key, reverseParams(delegate)); 7009 } else { 7010 return delegate(key, value); 7011 } 7012 }; 7013 } 7014 7015 function provider(name, provider_) { 7016 assertNotHasOwnProperty(name, 'service'); 7017 if (isFunction(provider_) || isArray(provider_)) { 7018 provider_ = providerInjector.instantiate(provider_); 7019 } 7020 if (!provider_.$get) { 7021 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 7022 } 7023 return providerCache[name + providerSuffix] = provider_; 7024 } 7025 7026 function enforceReturnValue(name, factory) { 7027 return function enforcedReturnValue() { 7028 var result = instanceInjector.invoke(factory, this); 7029 if (isUndefined(result)) { 7030 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 7031 } 7032 return result; 7033 }; 7034 } 7035 7036 function factory(name, factoryFn, enforce) { 7037 return provider(name, { 7038 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 7039 }); 7040 } 7041 7042 function service(name, constructor) { 7043 return factory(name, ['$injector', function($injector) { 7044 return $injector.instantiate(constructor); 7045 }]); 7046 } 7047 7048 function value(name, val) { return factory(name, valueFn(val), false); } 7049 7050 function constant(name, value) { 7051 assertNotHasOwnProperty(name, 'constant'); 7052 providerCache[name] = value; 7053 instanceCache[name] = value; 7054 } 7055 7056 function decorator(serviceName, decorFn) { 7057 var origProvider = providerInjector.get(serviceName + providerSuffix), 7058 orig$get = origProvider.$get; 7059 7060 origProvider.$get = function() { 7061 var origInstance = instanceInjector.invoke(orig$get, origProvider); 7062 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 7063 }; 7064 } 7065 7066 //////////////////////////////////// 7067 // Module Loading 7068 //////////////////////////////////// 7069 function loadModules(modulesToLoad) { 7070 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 7071 var runBlocks = [], moduleFn; 7072 forEach(modulesToLoad, function(module) { 7073 if (loadedModules.get(module)) return; 7074 loadedModules.put(module, true); 7075 7076 function runInvokeQueue(queue) { 7077 var i, ii; 7078 for (i = 0, ii = queue.length; i < ii; i++) { 7079 var invokeArgs = queue[i], 7080 provider = providerInjector.get(invokeArgs[0]); 7081 7082 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 7083 } 7084 } 7085 7086 try { 7087 if (isString(module)) { 7088 moduleFn = angularModule(module); 7089 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 7090 runInvokeQueue(moduleFn._invokeQueue); 7091 runInvokeQueue(moduleFn._configBlocks); 7092 } else if (isFunction(module)) { 7093 runBlocks.push(providerInjector.invoke(module)); 7094 } else if (isArray(module)) { 7095 runBlocks.push(providerInjector.invoke(module)); 7096 } else { 7097 assertArgFn(module, 'module'); 7098 } 7099 } catch (e) { 7100 if (isArray(module)) { 7101 module = module[module.length - 1]; 7102 } 7103 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 7104 // Safari & FF's stack traces don't contain error.message content 7105 // unlike those of Chrome and IE 7106 // So if stack doesn't contain message, we create a new string that contains both. 7107 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 7108 /* jshint -W022 */ 7109 e = e.message + '\n' + e.stack; 7110 } 7111 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 7112 module, e.stack || e.message || e); 7113 } 7114 }); 7115 return runBlocks; 7116 } 7117 7118 //////////////////////////////////// 7119 // internal Injector 7120 //////////////////////////////////// 7121 7122 function createInternalInjector(cache, factory) { 7123 7124 function getService(serviceName, caller) { 7125 if (cache.hasOwnProperty(serviceName)) { 7126 if (cache[serviceName] === INSTANTIATING) { 7127 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 7128 serviceName + ' <- ' + path.join(' <- ')); 7129 } 7130 return cache[serviceName]; 7131 } else { 7132 try { 7133 path.unshift(serviceName); 7134 cache[serviceName] = INSTANTIATING; 7135 return cache[serviceName] = factory(serviceName, caller); 7136 } catch (err) { 7137 if (cache[serviceName] === INSTANTIATING) { 7138 delete cache[serviceName]; 7139 } 7140 throw err; 7141 } finally { 7142 path.shift(); 7143 } 7144 } 7145 } 7146 7147 7148 function injectionArgs(fn, locals, serviceName) { 7149 var args = [], 7150 $inject = createInjector.$$annotate(fn, strictDi, serviceName); 7151 7152 for (var i = 0, length = $inject.length; i < length; i++) { 7153 var key = $inject[i]; 7154 if (typeof key !== 'string') { 7155 throw $injectorMinErr('itkn', 7156 'Incorrect injection token! Expected service name as string, got {0}', key); 7157 } 7158 args.push(locals && locals.hasOwnProperty(key) ? locals[key] : 7159 getService(key, serviceName)); 7160 } 7161 return args; 7162 } 7163 7164 function isClass(func) { 7165 // IE 9-11 do not support classes and IE9 leaks with the code below. 7166 if (msie <= 11) { 7167 return false; 7168 } 7169 // Workaround for MS Edge. 7170 // Check https://connect.microsoft.com/IE/Feedback/Details/2211653 7171 return typeof func === 'function' 7172 && /^(?:class\s|constructor\()/.test(Function.prototype.toString.call(func)); 7173 } 7174 7175 function invoke(fn, self, locals, serviceName) { 7176 if (typeof locals === 'string') { 7177 serviceName = locals; 7178 locals = null; 7179 } 7180 7181 var args = injectionArgs(fn, locals, serviceName); 7182 if (isArray(fn)) { 7183 fn = fn[fn.length - 1]; 7184 } 7185 7186 if (!isClass(fn)) { 7187 // http://jsperf.com/angularjs-invoke-apply-vs-switch 7188 // #5388 7189 return fn.apply(self, args); 7190 } else { 7191 args.unshift(null); 7192 return new (Function.prototype.bind.apply(fn, args))(); 7193 } 7194 } 7195 7196 7197 function instantiate(Type, locals, serviceName) { 7198 // Check if Type is annotated and use just the given function at n-1 as parameter 7199 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 7200 var ctor = (isArray(Type) ? Type[Type.length - 1] : Type); 7201 var args = injectionArgs(Type, locals, serviceName); 7202 // Empty object at position 0 is ignored for invocation with `new`, but required. 7203 args.unshift(null); 7204 return new (Function.prototype.bind.apply(ctor, args))(); 7205 } 7206 7207 7208 return { 7209 invoke: invoke, 7210 instantiate: instantiate, 7211 get: getService, 7212 annotate: createInjector.$$annotate, 7213 has: function(name) { 7214 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 7215 } 7216 }; 7217 } 7218} 7219 7220createInjector.$$annotate = annotate; 7221 7222/** 7223 * @ngdoc provider 7224 * @name $anchorScrollProvider 7225 * 7226 * @description 7227 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 7228 * {@link ng.$location#hash $location.hash()} changes. 7229 */ 7230function $AnchorScrollProvider() { 7231 7232 var autoScrollingEnabled = true; 7233 7234 /** 7235 * @ngdoc method 7236 * @name $anchorScrollProvider#disableAutoScrolling 7237 * 7238 * @description 7239 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 7240 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 7241 * Use this method to disable automatic scrolling. 7242 * 7243 * If automatic scrolling is disabled, one must explicitly call 7244 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 7245 * current hash. 7246 */ 7247 this.disableAutoScrolling = function() { 7248 autoScrollingEnabled = false; 7249 }; 7250 7251 /** 7252 * @ngdoc service 7253 * @name $anchorScroll 7254 * @kind function 7255 * @requires $window 7256 * @requires $location 7257 * @requires $rootScope 7258 * 7259 * @description 7260 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 7261 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 7262 * in the 7263 * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document). 7264 * 7265 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 7266 * match any anchor whenever it changes. This can be disabled by calling 7267 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 7268 * 7269 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 7270 * vertical scroll-offset (either fixed or dynamic). 7271 * 7272 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 7273 * {@link ng.$location#hash $location.hash()} will be used. 7274 * 7275 * @property {(number|function|jqLite)} yOffset 7276 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 7277 * positioned elements at the top of the page, such as navbars, headers etc. 7278 * 7279 * `yOffset` can be specified in various ways: 7280 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 7281 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 7282 * a number representing the offset (in pixels).<br /><br /> 7283 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 7284 * the top of the page to the element's bottom will be used as offset.<br /> 7285 * **Note**: The element will be taken into account only as long as its `position` is set to 7286 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adju
7286st 7287 * their height and/or positioning according to the viewport's size. 7288 * 7289 * <br /> 7290 * <div class="alert alert-warning"> 7291 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 7292 * not some child element. 7293 * </div> 7294 * 7295 * @example 7296 <example module="anchorScrollExample"> 7297 <file name="index.html"> 7298 <div id="scrollArea" ng-controller="ScrollController"> 7299 <a ng-click="gotoBottom()">Go to bottom</a> 7300 <a id="bottom"></a> You're at the bottom! 7301 </div> 7302 </file> 7303 <file name="script.js"> 7304 angular.module('anchorScrollExample', []) 7305 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 7306 function ($scope, $location, $anchorScroll) { 7307 $scope.gotoBottom = function() { 7308 // set the location.hash to the id of 7309 // the element you wish to scroll to. 7310 $location.hash('bottom'); 7311 7312 // call $anchorScroll() 7313 $anchorScroll(); 7314 }; 7315 }]); 7316 </file> 7317 <file name="style.css"> 7318 #scrollArea { 7319 height: 280px; 7320 overflow: auto; 7321 } 7322 7323 #bottom { 7324 display: block; 7325 margin-top: 2000px; 7326 } 7327 </file> 7328 </example> 7329 * 7330 * <hr /> 7331 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 7332 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 7333 * 7334 * @example 7335 <example module="anchorScrollOffsetExample"> 7336 <file name="index.html"> 7337 <div class="fixed-header" ng-controller="headerCtrl"> 7338 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 7339 Go to anchor {{x}} 7340 </a> 7341 </div> 7342 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 7343 Anchor {{x}} of 5 7344 </div> 7345 </file> 7346 <file name="script.js"> 7347 angular.module('anchorScrollOffsetExample', []) 7348 .run(['$anchorScroll', function($anchorScroll) { 7349 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 7350 }]) 7351 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 7352 function ($anchorScroll, $location, $scope) {
7353 $scope.gotoAnchor = function(x) { 7354 var newHash = 'anchor' + x; 7355 if ($location.hash() !== newHash) { 7356 // set the $location.hash to `newHash` and 7357 // $anchorScroll will automatically scroll to it 7358 $location.hash('anchor' + x); 7359 } else { 7360 // call $anchorScroll() explicitly, 7361 // since $location.hash hasn't changed 7362 $anchorScroll(); 7363 } 7364 }; 7365 } 7366 ]); 7367 </file> 7368 <file name="style.css"> 7369 body { 7370 padding-top: 50px; 7371 } 7372 7373 .anchor { 7374 border: 2px dashed DarkOrchid; 7375 padding: 10px 10px 200px 10px; 7376 } 7377 7378 .fixed-header { 7379 background-color: rgba(0, 0, 0, 0.2); 7380 height: 50px; 7381 position: fixed; 7382 top: 0; left: 0; right: 0; 7383 } 7384 7385 .fixed-header > a { 7386 display: inline-block; 7387 margin: 5px 15px; 7388 } 7389 </file> 7390 </example> 7391 */ 7392 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 7393 var document = $window.document; 7394 7395 // Helper function to get first anchor from a NodeList 7396 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 7397 // and working in all supported browsers.) 7398 function getFirstAnchor(list) { 7399 var result = null; 7400 Array.prototype.some.call(list, function(element) { 7401 if (nodeName_(element) === 'a') { 7402 result = element; 7403 return true; 7404 } 7405 }); 7406 return result; 7407 } 7408 7409 function getYOffset() { 7410 7411 var offset = scroll.yOffset; 7412 7413 if (isFunction(offset)) { 7414 offset = offset(); 7415 } else if (isElement(offset)) { 7416 var elem = offset[0]; 7417 var style = $window.getComputedStyle(elem); 7418 if (style.position !== 'fixed') { 7419 offset = 0; 7420 } else { 7421 offset = elem.getBoundingClientRect().bottom; 7422 } 7423 } else if (!isNumber(offset)) { 7424 offset = 0; 7425 } 7426 7427 return offset; 7428 } 7429 7430 function scrollTo(elem) { 7431 if (elem) { 7432 elem.scrollIntoView(); 7433 7434 var offset = getYOffset(); 7435 7436 if (offset) { 7437 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 7438 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 7439 // top of the viewport. 7440 // 7441 // IF the number of pixels from the top of `elem` to the end of the page's content is less 7442 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 7443 // way down the page. 7444 // 7445 // This is often the case for elements near the bottom of the page. 7446 // 7447 // In such cases we do not need to scroll the whole `offset` up, just the difference between 7448 // the top of the element and the offset, which is enough to align the top of `elem` at the 7449 // desired position. 7450 var elemTop = elem.getBoundingClientRect().top; 7451 $window.scrollBy(0, elemTop - offset); 7452 } 7453 } else { 7454 $window.scrollTo(0, 0); 7455 } 7456 } 7457 7458 function scroll(hash) { 7459 hash = isString(hash) ? hash : $location.hash(); 7460 var elm; 7461 7462 // empty hash, scroll to the top of the page 7463 if (!hash) scrollTo(null); 7464 7465 // element with given id 7466 else if ((elm = document.getElementById(hash))) scrollTo(elm); 7467 7468 // first anchor with given name :-D 7469 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 7470 7471 // no element and hash == 'top', scroll to the top of the page 7472 else if (hash === 'top') scrollTo(null); 7473 } 7474 7475 // does not scroll when user clicks on anchor link that is currently on 7476 // (no url change, no $location.hash() change), browser native does scroll 7477 if (autoScrollingEnabled) { 7478 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 7479 function autoScrollWatchAction(newVal, oldVal) { 7480 // skip the initial scroll if $location.hash is empty 7481 if (newVal === oldVal && newVal === '') return; 7482 7483 jqLiteDocumentLoaded(function() { 7484 $rootScope.$evalAsync(scroll); 7485 }); 7486 }); 7487 } 7488 7489 return scroll; 7490 }]; 7491} 7492 7493var $animateMinErr = minErr('$animate'); 7494var ELEMENT_NODE = 1; 7495var NG_ANIMATE_CLASSNAME = 'ng-animate'; 7496 7497function mergeClasses(a,b) { 7498 if (!a && !b) return ''; 7499 if (!a) return b; 7500 if (!b) return a; 7501 if (isArray(a)) a = a.join(' '); 7502 if (isArray(b)) b = b.join(' '); 7503 return a + ' ' + b; 7504} 7505 7506function extractElementNode(element) { 7507 for (var i = 0; i < element.length; i++) { 7508 var elm = element[i]; 7509 if (elm.nodeType === ELEMENT_NODE) { 7510 return elm; 7511 } 7512 } 7513} 7514 7515function splitClasses(classes) { 7516 if (isString(classes)) { 7517 classes = classes.split(' '); 7518 } 7519 7520 // Use createMap() to prevent class assumptions involving property names in 7521 // Object.prototype 7522 var obj = createMap(); 7523 forEach(classes, function(klass) { 7524 // sometimes the split leaves empty string values 7525 // incase extra spaces were applied to the options 7526 if (klass.length) { 7527 obj[klass] = true; 7528 } 7529 }); 7530 return obj; 7531} 7532 7533// if any other type of options value besides an Object value is 7534// passed into the $animate.method() animation then this helper code 7535// will be run which will ignore it. While this patch is not the 7536// greatest solution to this, a lot of existing plugins depend on 7537// $animate to either call the callback (< 1.2) or return a promise 7538// that can be changed. This helper function ensures that the options 7539// are wiped clean incase a callback function is provided. 7540function prepareAnimateOptions(options) { 7541 return isObject(options) 7542 ? options 7543 : {}; 7544} 7545
vendor: 4,186 bytes, lines 7546-7678
7546var $$CoreAnimateJsProvider = function() { 7547 this.$get = noop; 7548}; 7549 7550// this is prefixed with Core since it conflicts with 7551// the animateQueueProvider defined in ngAnimate/animateQueue.js 7552var $$CoreAnimateQueueProvider = function() { 7553 var postDigestQueue = new HashMap(); 7554 var postDigestElements = []; 7555 7556 this.$get = ['$$AnimateRunner', '$rootScope', 7557 function($$AnimateRunner, $rootScope) { 7558 return { 7559 enabled: noop, 7560 on: noop, 7561 off: noop, 7562 pin: noop, 7563 7564 push: function(element, event, options, domOperation) { 7565 domOperation && domOperation(); 7566 7567 options = options || {}; 7568 options.from && element.css(options.from); 7569 options.to && element.css(options.to); 7570 7571 if (options.addClass || options.removeClass) { 7572 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 7573 } 7574 7575 var runner = new $$AnimateRunner(); // jshint ignore:line 7576 7577 // since there are no animations to run the runner needs to be 7578 // notified that the animation call is complete. 7579 runner.complete(); 7580 return runner; 7581 } 7582 }; 7583 7584 7585 function updateData(data, classes, value) { 7586 var changed = false; 7587 if (classes) { 7588 classes = isString(classes) ? classes.split(' ') : 7589 isArray(classes) ? classes : []; 7590 forEach(classes, function(className) { 7591 if (className) { 7592 changed = true; 7593 data[className] = value; 7594 } 7595 }); 7596 } 7597 return changed; 7598 } 7599 7600 function handleCSSClassChanges() { 7601 forEach(postDigestElements, function(element) { 7602 var data = postDigestQueue.get(element); 7603 if (data) { 7604 var existing = splitClasses(element.attr('class')); 7605 var toAdd = ''; 7606 var toRemove = ''; 7607 forEach(data, function(status, className) { 7608 var hasClass = !!existing[className]; 7609 if (status !== hasClass) { 7610 if (status) { 7611 toAdd += (toAdd.length ? ' ' : '') + className; 7612 } else { 7613 toRemove += (toRemove.length ? ' ' : '') + className; 7614 } 7615 } 7616 }); 7617 7618 forEach(element, function(elm) { 7619 toAdd && jqLiteAddClass(elm, toAdd); 7620 toRemove && jqLiteRemoveClass(elm, toRemove); 7621 }); 7622 postDigestQueue.remove(element); 7623 } 7624 }); 7625 postDigestElements.length = 0; 7626 } 7627 7628 7629 function addRemoveClassesPostDigest(element, add, remove) { 7630 var data = postDigestQueue.get(element) || {}; 7631 7632 var classesAdded = updateData(data, add, true); 7633 var classesRemoved = updateData(data, remove, false); 7634 7635 if (classesAdded || classesRemoved) { 7636 7637 postDigestQueue.put(element, data); 7638 postDigestElements.push(element); 7639 7640 if (postDigestElements.length === 1) { 7641 $rootScope.$$postDigest(handleCSSClassChanges); 7642 } 7643 } 7644 } 7645 }]; 7646}; 7647 7648/** 7649 * @ngdoc provider 7650 * @name $animateProvider 7651 * 7652 * @description 7653 * Default implementation of $animate that doesn't perform any animations, instead just 7654 * synchronously performs DOM updates and resolves the returned runner promise. 7655 * 7656 * In order to enable animations the `ngAnimate` module has to be loaded. 7657 * 7658 * To see the functional implementation check out `src/ngAnimate/animate.js`. 7659 */ 7660var $AnimateProvider = ['$provide', function($provide) { 7661 var provider = this; 7662 7663 this.$$registeredAnimations = Object.create(null); 7664 7665 /** 7666 * @ngdoc method 7667 * @name $animateProvider#register 7668 * 7669 * @description 7670 * Registers a new injectable animation factory function. The factory function produces the 7671 * animation object which contains callback functions for each event that is expected to be 7672 * animated. 7673 * 7674 * * `eventFn`: `function(element, ... , doneFunction, options)` 7675 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 7676 * on the type of animation additional arguments will be injected into the animation function. The 7677 * list below explains the function signatures for the different animation methods: 7678 *
7679 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 7680 * - addClass: function(element, addedClasses, doneFunction, options) 7681 * - removeClass: function(element, removedClasses, doneFunction, options) 7682 * - enter, leave, move: function(element, doneFunction, options) 7683 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 7684 * 7685 * Make sure to trigger the `doneFunction` once the animation is fully complete. 7686 * 7687 * ```js 7688 * return { 7689 * //enter, leave, move signature 7690 * eventFn : function(element, done, options) { 7691 * //code to run the animation 7692 * //once complete, then run done() 7693 * return function endFunction(wasCancelled) { 7694 * //code to cancel the animation 7695 * } 7696 * } 7697 * } 7698 * ``` 7699 * 7700 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 7701 * @param {Function} factory The factory function that will be executed to return the animation 7702 * object. 7703 */ 7704 this.register = function(name, factory) { 7705 if (name && name.charAt(0) !== '.') { 7706 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 7707 } 7708 7709 var key = name + '-animation'; 7710 provider.$$registeredAnimations[name.substr(1)] = key; 7711 $provide.factory(key, factory); 7712 }; 7713 7714 /** 7715 * @ngdoc method 7716 * @name $animateProvider#classNameFilter 7717 * 7718 * @description 7719 * Sets and/or returns the CSS class regular expression that is checked when performing 7720 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 7721 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 7722 * When setting the `classNameFilter` value, animations will only be performed on elements 7723 * that successfully match the filter expression. This in turn can boost performance 7724 * for low-powered devices as well as applications containing a lot of structural operations. 7725 * @param {RegExp=} expression The className expression which will be checked against all animations 7726 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 7727 */ 7728 this.classNameFilter = function(expression) { 7729 if (arguments.length === 1) { 7730 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 7731 if (this.$$classNameFilter) { 7732 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 7733 if (reservedRegex.test(this.$$classNameFilter.toString())) { 7734 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 7735 7736 } 7737 } 7738 } 7739 return this.$$classNameFilter; 7740 }; 7741 7742 this.$get = ['$$animateQueue', function($$animateQueue) { 7743 function domInsert(element, parentElement, afterElement) { 7744 // if for some reason the previous element was removed 7745 // from the dom sometime before this code runs then let's 7746 // just stick to using the parent element as the anchor 7747 if (afterElement) { 7748 var afterNode = extractElementNode(afterElement); 7749 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 7750 afterElement = null; 7751 } 7752 } 7753 afterElement ? afterElement.after(element) : parentElement.prepend(element); 7754 } 7755 7756 /** 7757 * @ngdoc service 7758 * @name $animate 7759 * @description The $animate service exposes a series of DOM utility methods that provide support 7760 * for animation hooks. The default behavior is the application of DOM operations, however, 7761 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 7762 * to ensure that animation runs with the triggered DOM operation. 7763 * 7764 * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't 7765 * included and only when it is active then the animation hooks that `$animate` triggers will be 7766 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 7767 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 7768 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 7769 * 7770 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 7771 * 7772 * To learn more about enabling animation support, click here to visit the 7773 * {@link ngAnimate ngAnimate module page}. 7774 */ 7775 return { 7776 // we don't call it directly since non-existant arguments may 7777 // be interpreted as null within the sub enabled function 7778 7779 /** 7780 * 7781 * @ngdoc method 7782 * @name $animate#on 7783 * @kind function
7784 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 7785 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 7786 * is fired with the following params: 7787 * 7788 * ```js 7789 * $animate.on('enter', container, 7790 * function callback(element, phase) { 7791 * // cool we detected an enter animation within the container 7792 * } 7793 * ); 7794 * ``` 7795 * 7796 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 7797 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 7798 * as well as among its children 7799 * @param {Function} callback the callback function that will be fired when the listener is triggered 7800 * 7801 * The arguments present in the callback function are: 7802 * * `element` - The captured DOM element that the animation was fired on. 7803 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 7804 */ 7805 on: $$animateQueue.on, 7806 7807 /** 7808 * 7809 * @ngdoc method 7810 * @name $animate#off 7811 * @kind function 7812 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 7813 * can be used in three different ways depending on the arguments: 7814 * 7815 * ```js 7816 * // remove all the animation event listeners listening for `enter` 7817 * $animate.off('enter'); 7818 * 7819 * // remove listeners for all animation events from the container element 7820 * $animate.off(container); 7821 * 7822 * // remove all the animation event listeners listening for `enter` on the given element and its children 7823 * $animate.off('enter', container); 7824 * 7825 * // remove the event listener function provided by `callback` that is set 7826 * // to listen for `enter` on the given `container` as well as its children 7827 * $animate.off('enter', container, callback); 7828 * ``` 7829 * 7830 * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move, 7831 * addClass, removeClass, etc...), or the container element. If it is the element, all other 7832 * arguments are ignored. 7833 * @param {DOMElement=} container the container element the event listener was placed on 7834 * @param {Function=} callback the callback function that was registered as the listener 7835 */ 7836 off: $$animateQueue.off, 7837 7838 /** 7839 * @ngdoc method 7840 * @name $animate#pin 7841 * @kind function 7842 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 7843 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 7844 * element despite being outside the realm of the application or within another application. Say for example if the application 7845 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 7846 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 7847 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 7848 * 7849 * Note that this feature is only active when the `ngAnimate` module is used. 7850 * 7851 * @param {DOMElement} element the external element that will be pinned 7852 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 7853 */ 7854 pin: $$animateQueue.pin, 7855 7856 /** 7857 * 7858 * @ngdoc method 7859 * @name $animate#enabled 7860 * @kind function 7861 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 7862 * function can be called in four ways: 7863 * 7864 * ```js 7865 * // returns true or false 7866 * $animate.enabled(); 7867 * 7868 * // changes the enabled state for all animations 7869 * $animate.enabled(false); 7870 * $animate.enabled(true); 7871 * 7872 * // returns true or false if animations are enabled for an element 7873 * $animate.enabled(element); 7874 * 7875 * // changes the enabled state for an element and its children 7876 * $animate.enabled(element, true); 7877 * $animate.enabled(element, false); 7878 * ``` 7879 * 7880 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 7881 * @param {boolean=} enabled whether or not the animations will be enabled for the element 7882 * 7883 * @return {boolean} whether or not animations are enabled 7884 */ 7885 enabled: $$animateQueue.enabled, 7886 7887 /** 7888 * @ngdoc method 7889 * @name $animate#cancel 7890 * @kind function
7891 * @description Cancels the provided animation. 7892 * 7893 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 7894 */ 7895 cancel: function(runner) { 7896 runner.end && runner.end(); 7897 }, 7898 7899 /** 7900 * 7901 * @ngdoc method 7902 * @name $animate#enter 7903 * @kind function 7904 * @description Inserts the element into the DOM either after the `after` element (if provided) or 7905 * as the first child within the `parent` element and then triggers an animation. 7906 * A promise is returned that will be resolved during the next digest once the animation 7907 * has completed. 7908 * 7909 * @param {DOMElement} element the element which will be inserted into the DOM 7910 * @param {DOMElement} parent the parent element which will append the element as 7911 * a child (so long as the after element is not present) 7912 * @param {DOMElement=} after the sibling element after which the element will be appended 7913 * @param {object=} options an optional collection of options/styles that will be applied to the element 7914 * 7915 * @return {Promise} the animation callback promise 7916 */ 7917 enter: function(element, parent, after, options) { 7918 parent = parent && jqLite(parent); 7919 after = after && jqLite(after); 7920 parent = parent || after.parent(); 7921 domInsert(element, parent, after); 7922 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 7923 }, 7924 7925 /** 7926 * 7927 * @ngdoc method 7928 * @name $animate#move 7929 * @kind function 7930 * @description Inserts (moves) the element into its new position in the DOM either after 7931 * the `after` element (if provided) or as the first child within the `parent` element 7932 * and then triggers an animation. A promise is returned that will be resolved 7933 * during the next digest once the animation has completed. 7934 * 7935 * @param {DOMElement} element the element which will be moved into the new DOM position 7936 * @param {DOMElement} parent the parent element which will append the element as 7937 * a child (so long as the after element is not present) 7938 * @param {DOMElement=} after the sibling element after which the element will be appended 7939 * @param {object=} options an optional collection of options/styles that will be applied to the element 7940 * 7941 * @return {Promise} the animation callback promise 7942 */ 7943 move: function(element, parent, after, options) { 7944 parent = parent && jqLite(parent); 7945 after = after && jqLite(after); 7946 parent = parent || after.parent(); 7947 domInsert(element, parent, after); 7948 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 7949 }, 7950 7951 /** 7952 * @ngdoc method 7953 * @name $animate#leave 7954 * @kind function 7955 * @description Triggers an animation and then removes the element from the DOM. 7956 * When the function is called a promise is returned that will be resolved during the next 7957 * digest once the animation has completed. 7958 * 7959 * @param {DOMElement} element the element which will be removed from the DOM 7960 * @param {object=} options an optional collection of options/styles that will be applied to the element 7961 * 7962 * @return {Promise} the animation callback promise 7963 */ 7964 leave: function(element, options) { 7965 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 7966 element.remove(); 7967 }); 7968 }, 7969 7970 /** 7971 * @ngdoc method 7972 * @name $animate#addClass 7973 * @kind function 7974 * 7975 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 7976 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 7977 * animation if element already contains the CSS class or if the class is removed at a later step. 7978 * Note that class-based animations are treated differently compared to structural animations 7979 * (like enter, move and leave) since the CSS classes may be added/removed at different points 7980 * depending if CSS or JavaScript animations are used. 7981 * 7982 * @param {DOMElement} element the element which the CSS classes will be applied to 7983 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 7984 * @param {object=} options an optional collection of options/styles that will be applied to the element 7985 * 7986 * @return {Promise} the animation callback promise 7987 */ 7988 addClass: function(element, className, options) { 7989 options = prepareAnimateOptions(options); 7990 options.addClass = mergeClasses(options.addclass, className); 7991 return $$animateQueue.push(element, 'addClass', options); 7992 }, 7993 7994 /** 7995 * @ngdoc method 7996 * @name $animate#removeClass 7997 * @kind function 7998 *
7999 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 8000 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 8001 * animation if element does not contain the CSS class or if the class is added at a later step. 8002 * Note that class-based animations are treated differently compared to structural animations 8003 * (like enter, move and leave) since the CSS classes may be added/removed at different points 8004 * depending if CSS or JavaScript animations are used. 8005 * 8006 * @param {DOMElement} element the element which the CSS classes will be applied to 8007 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 8008 * @param {object=} options an optional collection of options/styles that will be applied to the element 8009 * 8010 * @return {Promise} the animation callback promise 8011 */ 8012 removeClass: function(element, className, options) { 8013 options = prepareAnimateOptions(options); 8014 options.removeClass = mergeClasses(options.removeClass, className); 8015 return $$animateQueue.push(element, 'removeClass', options); 8016 }, 8017 8018 /** 8019 * @ngdoc method 8020 * @name $animate#setClass 8021 * @kind function 8022 * 8023 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 8024 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 8025 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 8026 * passed. Note that class-based animations are treated differently compared to structural animations 8027 * (like enter, move and leave) since the CSS classes may be added/removed at different points 8028 * depending if CSS or JavaScript animations are used. 8029 * 8030 * @param {DOMElement} element the element which the CSS classes will be applied to 8031 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 8032 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 8033 * @param {object=} options an optional collection of options/styles that will be applied to the element 8034 * 8035 * @return {Promise} the animation callback promise 8036 */ 8037 setClass: function(element, add, remove, options) { 8038 options = prepareAnimateOptions(options); 8039 options.addClass = mergeClasses(options.addClass, add); 8040 options.removeClass = mergeClasses(options.removeClass, remove); 8041 return $$animateQueue.push(element, 'setClass', options); 8042 }, 8043 8044 /** 8045 * @ngdoc method 8046 * @name $animate#animate 8047 * @kind function 8048 * 8049 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 8050 * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take 8051 * on the provided styles. For example, if a transition animation is set for the given classNamem, then the provided `from` and 8052 * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding 8053 * style in `to`, the style in `from` is applied immediately, and no animation is run. 8054 * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate` 8055 * method (or as part of the `options` parameter): 8056 * 8057 * ```js 8058 * ngModule.animation('.my-inline-animation', function() { 8059 * return { 8060 * animate : function(element, from, to, done, options) { 8061 * //animation 8062 * done(); 8063 * } 8064 * } 8065 * }); 8066 * ``` 8067 * 8068 * @param {DOMElement} element the element which the CSS styles will be applied to 8069 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 8070 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 8071 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 8072 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 8073 * (Note that if no animation is detected then this value will not be applied to the element.) 8074 * @param {object=} options an optional collection of options/styles that will be applied to the element 8075 * 8076 * @return {Promise} the animation callback promise 8077 */ 8078 animate: function(element, from, to, className, options) { 8079 options = prepareAnimateOptions(options); 8080 options.from = options.from ? extend(options.from, from) : from; 8081 options.to = options.to ? extend(options.to, to) : to; 8082 8083 className = className || 'ng-inline-animate';
vendor: 4,147 bytes, lines 8084-8261
8084 options.tempClasses = mergeClasses(options.tempClasses, className); 8085 return $$animateQueue.push(element, 'animate', options); 8086 } 8087 }; 8088 }]; 8089}]; 8090 8091var $$AnimateAsyncRunFactoryProvider = function() { 8092 this.$get = ['$$rAF', function($$rAF) { 8093 var waitQueue = []; 8094 8095 function waitForTick(fn) { 8096 waitQueue.push(fn); 8097 if (waitQueue.length > 1) return; 8098 $$rAF(function() { 8099 for (var i = 0; i < waitQueue.length; i++) { 8100 waitQueue[i](); 8101 } 8102 waitQueue = []; 8103 }); 8104 } 8105 8106 return function() { 8107 var passed = false; 8108 waitForTick(function() { 8109 passed = true; 8110 }); 8111 return function(callback) { 8112 passed ? callback() : waitForTick(callback); 8113 }; 8114 }; 8115 }]; 8116}; 8117 8118var $$AnimateRunnerFactoryProvider = function() { 8119 this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', 8120 function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { 8121 8122 var INITIAL_STATE = 0; 8123 var DONE_PENDING_STATE = 1; 8124 var DONE_COMPLETE_STATE = 2; 8125 8126 AnimateRunner.chain = function(chain, callback) { 8127 var index = 0; 8128 8129 next(); 8130 function next() { 8131 if (index === chain.length) { 8132 callback(true); 8133 return; 8134 } 8135 8136 chain[index](function(response) { 8137 if (response === false) { 8138 callback(false); 8139 return; 8140 } 8141 index++; 8142 next(); 8143 }); 8144 } 8145 }; 8146 8147 AnimateRunner.all = function(runners, callback) { 8148 var count = 0; 8149 var status = true; 8150 forEach(runners, function(runner) { 8151 runner.done(onProgress); 8152 }); 8153 8154 function onProgress(response) { 8155 status = status && response; 8156 if (++count === runners.length) { 8157 callback(status); 8158 } 8159 } 8160 }; 8161 8162 function AnimateRunner(host) { 8163 this.setHost(host); 8164 8165 var rafTick = $$animateAsyncRun(); 8166 var timeoutTick = function(fn) { 8167 $timeout(fn, 0, false); 8168 }; 8169 8170 this._doneCallbacks = []; 8171 this._tick = function(fn) { 8172 var doc = $document[0]; 8173 8174 // the document may not be ready or attached 8175 // to the module for some internal tests 8176 if (doc && doc.hidden) { 8177 timeoutTick(fn); 8178 } else { 8179 rafTick(fn); 8180 } 8181 }; 8182 this._state = 0; 8183 } 8184 8185 AnimateRunner.prototype = { 8186 setHost: function(host) { 8187 this.host = host || {}; 8188 }, 8189 8190 done: function(fn) { 8191 if (this._state === DONE_COMPLETE_STATE) { 8192 fn(); 8193 } else { 8194 this._doneCallbacks.push(fn); 8195 } 8196 }, 8197 8198 progress: noop, 8199 8200 getPromise: function() { 8201 if (!this.promise) { 8202 var self = this; 8203 this.promise = $q(function(resolve, reject) { 8204 self.done(function(status) { 8205 status === false ? reject() : resolve(); 8206 }); 8207 }); 8208 } 8209 return this.promise; 8210 }, 8211 8212 then: function(resolveHandler, rejectHandler) { 8213 return this.getPromise().then(resolveHandler, rejectHandler); 8214 }, 8215 8216 'catch': function(handler) { 8217 return this.getPromise()['catch'](handler); 8218 }, 8219 8220 'finally': function(handler) { 8221 return this.getPromise()['finally'](handler); 8222 }, 8223 8224 pause: function() { 8225 if (this.host.pause) { 8226 this.host.pause(); 8227 } 8228 }, 8229 8230 resume: function() { 8231 if (this.host.resume) { 8232 this.host.resume(); 8233 } 8234 }, 8235 8236 end: function() { 8237 if (this.host.end) { 8238 this.host.end(); 8239 } 8240 this._resolve(true); 8241 }, 8242 8243 cancel: function() { 8244 if (this.host.cancel) { 8245 this.host.cancel(); 8246 } 8247 this._resolve(false); 8248 }, 8249 8250 complete: function(response) { 8251 var self = this; 8252 if (self._state === INITIAL_STATE) { 8253 self._state = DONE_PENDING_STATE; 8254 self._tick(function() { 8255 self._resolve(response); 8256 }); 8257 } 8258 }, 8259 8260 _resolve: function(response) { 8261 if (this._state !== DONE_COMPLETE_STATE) {
8262 forEach(this._doneCallbacks, function(fn) { 8263 fn(response); 8264 }); 8265 this._doneCallbacks.length = 0; 8266 this._state = DONE_COMPLETE_STATE; 8267 } 8268 } 8269 }; 8270 8271 return AnimateRunner; 8272 }]; 8273}; 8274 8275/** 8276 * @ngdoc service 8277 * @name $animateCss 8278 * @kind object 8279 * 8280 * @description 8281 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 8282 * then the `$animateCss` service will actually perform animations. 8283 * 8284 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 8285 */ 8286var $CoreAnimateCssProvider = function() { 8287 this.$get = ['$$rAF', '$q', '$$AnimateRunner', function($$rAF, $q, $$AnimateRunner) { 8288 8289 return function(element, initialOptions) { 8290 // all of the animation functions should create 8291 // a copy of the options data, however, if a 8292 // parent service has already created a copy then 8293 // we should stick to using that 8294 var options = initialOptions || {}; 8295 if (!options.$$prepared) { 8296 options = copy(options); 8297 } 8298 8299 // there is no point in applying the styles since 8300 // there is no animation that goes on at all in 8301 // this version of $animateCss. 8302 if (options.cleanupStyles) { 8303 options.from = options.to = null; 8304 } 8305 8306 if (options.from) { 8307 element.css(options.from); 8308 options.from = null; 8309 } 8310 8311 /* jshint newcap: false */ 8312 var closed, runner = new $$AnimateRunner(); 8313 return { 8314 start: run, 8315 end: run 8316 }; 8317 8318 function run() { 8319 $$rAF(function() { 8320 applyAnimationContents(); 8321 if (!closed) { 8322 runner.complete(); 8323 } 8324 closed = true; 8325 }); 8326 return runner; 8327 } 8328 8329 function applyAnimationContents() { 8330 if (options.addClass) { 8331 element.addClass(options.addClass); 8332 options.addClass = null; 8333 } 8334 if (options.removeClass) { 8335 element.removeClass(options.removeClass); 8336 options.removeClass = null; 8337 } 8338 if (options.to) { 8339 element.css(options.to); 8340 options.to = null; 8341 } 8342 } 8343 }; 8344 }]; 8345}; 8346 8347/* global stripHash: true */ 8348 8349/** 8350 * ! This is a private undocumented service ! 8351 * 8352 * @name $browser 8353 * @requires $log 8354 * @description 8355 * This object has two goals: 8356 * 8357 * - hide all the global state in the browser caused by the window object 8358 * - abstract away all the browser specific features and inconsistencies 8359 * 8360 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 8361 * service, which can be used for convenient testing of the application without the interaction with 8362 * the real browser apis. 8363 */ 8364/** 8365 * @param {object} window The global window object. 8366 * @param {object} document jQuery wrapped document. 8367 * @param {object} $log window.console or an object with the same interface. 8368 * @param {object} $sniffer $sniffer service 8369 */ 8370function Browser(window, document, $log, $sniffer) { 8371 var self = this, 8372 location = window.location, 8373 history = window.history, 8374 setTimeout = window.setTimeout, 8375 clearTimeout = window.clearTimeout, 8376 pendingDeferIds = {}; 8377 8378 self.isMock = false; 8379 8380 var outstandingRequestCount = 0; 8381 var outstandingRequestCallbacks = []; 8382 8383 // TODO(vojta): remove this temporary api 8384 self.$$completeOutstandingRequest = completeOutstandingRequest; 8385 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 8386 8387 /** 8388 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 8389 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 8390 */ 8391 function completeOutstandingRequest(fn) { 8392 try { 8393 fn.apply(null, sliceArgs(arguments, 1)); 8394 } finally { 8395 outstandingRequestCount--; 8396 if (outstandingRequestCount === 0) { 8397 while (outstandingRequestCallbacks.length) { 8398 try { 8399 outstandingRequestCallbacks.pop()(); 8400 } catch (e) { 8401 $log.error(e); 8402 } 8403 } 8404 } 8405 } 8406 } 8407 8408 function getHash(url) { 8409 var index = url.indexOf('#'); 8410 return index === -1 ? '' : url.substr(index); 8411 } 8412 8413 /** 8414 * @private 8415 * Note: this method is used only by scenario runner 8416 * TODO(vojta): prefix this method with $$ ? 8417 * @param {function()} callback Function that will be called when no outstanding request 8418 */ 8419 self.notifyWhenNoOutstandingRequests = function(callback) { 8420 if (outstandingRequestCount === 0) { 8421 callback(); 8422 } else { 8423 outstandingRequestCallbacks.push(callback); 8424 } 8425 }; 8426 8427 ////////////////////////////////////////////////////////////// 8428 // URL API 8429 ////////////////////////////////////////////////////////////// 8430 8431 var cachedState, lastHistoryState, 8432 lastBrowserUrl = location.href, 8433 baseElement = document.find('base'), 8434 pendingLocation = null, 8435 getCurrentState = !$sniffer.history ? noop : function getCurrentState() { 8436 try { 8437 return history.state; 8438 } catch (e) { 8439 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 8440 } 8441 }; 8442 8443 cacheState(); 8444 lastHistoryState = cachedState; 8445 8446 /** 8447 * @name $browser#url 8448 * 8449 * @description 8450 * GETTER: 8451 * Without any argument, this method just returns current value of location.href. 8452 * 8453 * SETTER: 8454 * With at least one argument, this method sets url to new value. 8455 * If html5 history api supported, pushState/replaceState is used, otherwise 8456 * location.href/location.replace is used. 8457 * Returns its own instance to allow chaining 8458 * 8459 * NOTE: this api is intended for use only by the $location service. Please use the 8460 * {@link ng.$location $location service} to change url. 8461 * 8462 * @param {string} url New url (when used as setter) 8463 * @param {boolean=} replace Should new url replace current history record? 8464 * @param {object=} state object to use with pushState/replaceState 8465 */ 8466 self.url = function(url, replace, state) { 8467 // In modern browsers `history.state` is `null` by default; treating it separately 8468 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 8469 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 8470 if (isUndefined(state)) { 8471 state = null; 8472 } 8473 8474 // Android Browser BFCache causes location, history reference to become stale. 8475 if (location !== window.location) location = window.location; 8476 if (history !== window.history) history = window.history; 8477 8478 // setter 8479 if (url) { 8480 var sameState = lastHistoryState === state; 8481 8482 // Don't change anything if previous and current URLs and states match. This also prevents 8483 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 8484 // See https://github.com/angular/angular.js/commit/ffb2701 8485 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 8486 return self; 8487 } 8488 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 8489 lastBrowserUrl = url; 8490 lastHistoryState = state; 8491 // Don't use history API if only the hash changed 8492 // due to a bug in IE10/IE11 which leads 8493 // to not firing a `hashchange` nor `popstate` event 8494 // in some cases (see #9143). 8495 if ($sniffer.history && (!sameBase || !sameState)) { 8496 history[replace ? 'replaceState' : 'pushState'](state, '', url); 8497 cacheState(); 8498 // Do the assignment again so that those two variables are referentially identical. 8499 lastHistoryState = cachedState; 8500 } else { 8501 if (!sameBase || pendingLocation) { 8502 pendingLocation = url; 8503 } 8504 if (replace) { 8505 location.replace(url); 8506 } else if (!sameBase) { 8507 location.href = url; 8508 } else { 8509 location.hash = getHash(url); 8510 } 8511 if (location.href !== url) { 8512 pendingLocation = url; 8513 } 8514 } 8515 return self; 8516 // getter 8517 } else { 8518 // - pendingLocation is needed as browsers don't allow to read out 8519 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 8520 // https://openradar.appspot.com/22186109). 8521 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 8522 return pendingLocation || location.href.replace(/%27/g,"'"); 8523 } 8524 }; 8525 8526 /** 8527 * @name $browser#state 8528 * 8529 * @description 8530 * This method is a getter. 8531 * 8532 * Return history.state or null if history.state is undefined. 8533 * 8534 * @returns {object} state 8535 */ 8536 self.state = function() { 8537 return cachedState; 8538 }; 8539 8540 var urlChangeListeners = [], 8541 urlChangeInit = false;
8542 8543 function cacheStateAndFireUrlChange() { 8544 pendingLocation = null; 8545 cacheState(); 8546 fireUrlChange(); 8547 } 8548 8549 // This variable should be used *only* inside the cacheState function. 8550 var lastCachedState = null; 8551 function cacheState() { 8552 // This should be the only place in $browser where `history.state` is read. 8553 cachedState = getCurrentState(); 8554 cachedState = isUndefined(cachedState) ? null : cachedState; 8555 8556 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 8557 if (equals(cachedState, lastCachedState)) { 8558 cachedState = lastCachedState; 8559 } 8560 lastCachedState = cachedState; 8561 } 8562 8563 function fireUrlChange() { 8564 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 8565 return; 8566 } 8567 8568 lastBrowserUrl = self.url(); 8569 lastHistoryState = cachedState; 8570 forEach(urlChangeListeners, function(listener) { 8571 listener(self.url(), cachedState); 8572 }); 8573 } 8574 8575 /** 8576 * @name $browser#onUrlChange 8577 * 8578 * @description 8579 * Register callback function that will be called, when url changes. 8580 * 8581 * It's only called when the url is changed from outside of angular: 8582 * - user types different url into address bar 8583 * - user clicks on history (forward/back) button 8584 * - user clicks on a link 8585 * 8586 * It's not called when url is changed by $browser.url() method 8587 * 8588 * The listener gets called with new url as parameter. 8589 * 8590 * NOTE: this api is intended for use only by the $location service. Please use the 8591 * {@link ng.$location $location service} to monitor url changes in angular apps. 8592 * 8593 * @param {function(string)} listener Listener function to be called when url changes. 8594 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 8595 */ 8596 self.onUrlChange = function(callback) { 8597 // TODO(vojta): refactor to use node's syntax for events 8598 if (!urlChangeInit) { 8599 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 8600 // don't fire popstate when user change the address bar and don't fire hashchange when url 8601 // changed by push/replaceState 8602 8603 // html5 history api - popstate event 8604 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 8605 // hashchange event 8606 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 8607 8608 urlChangeInit = true; 8609 } 8610 8611 urlChangeListeners.push(callback); 8612 return callback; 8613 }; 8614 8615 /** 8616 * @private 8617 * Remove popstate and hashchange handler from window. 8618 * 8619 * NOTE: this api is intended for use only by $rootScope. 8620 */ 8621 self.$$applicationDestroyed = function() { 8622 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 8623 }; 8624 8625 /** 8626 * Checks whether the url has changed outside of Angular. 8627 * Needs to be exported to be able to check for changes that have been done in sync, 8628 * as hashchange/popstate events fire in async. 8629 */ 8630 self.$$checkUrlChange = fireUrlChange; 8631 8632 ////////////////////////////////////////////////////////////// 8633 // Misc API 8634 ////////////////////////////////////////////////////////////// 8635 8636 /** 8637 * @name $browser#baseHref 8638 * 8639 * @description 8640 * Returns current <base href> 8641 * (always relative - without domain) 8642 * 8643 * @returns {string} The current base href 8644 */ 8645 self.baseHref = function() { 8646 var href = baseElement.attr('href'); 8647 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 8648 }; 8649 8650 /** 8651 * @name $browser#defer 8652 * @param {function()} fn A function, who's execution should be deferred. 8653 * @param {number=} [delay=0] of milliseconds to defer the function execution. 8654 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 8655 * 8656 * @description 8657 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 8658 * 8659 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 8660 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 8661 * via `$browser.defer.flush()`. 8662 * 8663 */ 8664 self.defer = function(fn, delay) { 8665 var timeoutId; 8666 outstandingRequestCount++; 8667 timeoutId = setTimeout(function() {
8668 delete pendingDeferIds[timeoutId]; 8669 completeOutstandingRequest(fn); 8670 }, delay || 0); 8671 pendingDeferIds[timeoutId] = true; 8672 return timeoutId; 8673 }; 8674 8675 8676 /** 8677 * @name $browser#defer.cancel 8678 * 8679 * @description 8680 * Cancels a deferred task identified with `deferId`. 8681 * 8682 * @param {*} deferId Token returned by the `$browser.defer` function. 8683 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 8684 * canceled. 8685 */ 8686 self.defer.cancel = function(deferId) { 8687 if (pendingDeferIds[deferId]) { 8688 delete pendingDeferIds[deferId]; 8689 clearTimeout(deferId); 8690 completeOutstandingRequest(noop); 8691 return true; 8692 } 8693 return false; 8694 }; 8695 8696} 8697 8698function $BrowserProvider() { 8699 this.$get = ['$window', '$log', '$sniffer', '$document', 8700 function($window, $log, $sniffer, $document) { 8701 return new Browser($window, $document, $log, $sniffer); 8702 }]; 8703} 8704 8705/** 8706 * @ngdoc service 8707 * @name $cacheFactory 8708 * 8709 * @description 8710 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 8711 * them. 8712 * 8713 * ```js 8714 * 8715 * var cache = $cacheFactory('cacheId'); 8716 * expect($cacheFactory.get('cacheId')).toBe(cache); 8717 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 8718 * 8719 * cache.put("key", "value"); 8720 * cache.put("another key", "another value"); 8721 * 8722 * // We've specified no options on creation 8723 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 8724 * 8725 * ``` 8726 * 8727 * 8728 * @param {string} cacheId Name or id of the newly created cache. 8729 * @param {object=} options Options object that specifies the cache behavior. Properties: 8730 * 8731 * - `{number=}` `capacity` â turns the cache into LRU cache. 8732 * 8733 * @returns {object} Newly created cache object with the following set of methods: 8734 * 8735 * - `{object}` `info()` â Returns id, size, and options of cache. 8736 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 8737 * it. 8738 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 8739 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 8740 * - `{void}` `removeAll()` â Removes all cached values. 8741 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 8742 * 8743 * @example 8744 <example module="cacheExampleApp"> 8745 <file name="index.html"> 8746 <div ng-controller="CacheController"> 8747 <input ng-model="newCacheKey" placeholder="Key"> 8748 <input ng-model="newCacheValue" placeholder="Value"> 8749 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 8750 8751 <p ng-if="keys.length">Cached Values</p> 8752 <div ng-repeat="key in keys"> 8753 <span ng-bind="key"></span> 8754 <span>: </span> 8755 <b ng-bind="cache.get(key)"></b> 8756 </div> 8757 8758 <p>Cache Info</p> 8759 <div ng-repeat="(key, value) in cache.info()"> 8760 <span ng-bind="key"></span> 8761 <span>: </span> 8762 <b ng-bind="value"></b> 8763 </div> 8764 </div> 8765 </file> 8766 <file name="script.js"> 8767 angular.module('cacheExampleApp', []). 8768 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 8769 $scope.keys = []; 8770 $scope.cache = $cacheFactory('cacheId'); 8771 $scope.put = function(key, value) { 8772 if (angular.isUndefined($scope.cache.get(key))) { 8773 $scope.keys.push(key); 8774 } 8775 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 8776 }; 8777 }]); 8778 </file> 8779 <file name="style.css"> 8780 p { 8781 margin: 10px 0 3px; 8782 } 8783 </file> 8784 </example> 8785 */ 8786function $CacheFactoryProvider() { 8787 8788 this.$get = function() { 8789 var caches = {}; 8790 8791 function cacheFactory(cacheId, options) { 8792 if (cacheId in caches) { 8793 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 8794 } 8795 8796 var size = 0, 8797 stats = extend({}, options, {id: cacheId}), 8798 data = createMap(), 8799 capacity = (options && options.capacity) || Number.MAX_VALUE, 8800 lruHash = createMap(), 8801 freshEnd = null, 8802 staleEnd = null; 8803 8804 /** 8805 * @ngdoc type 8806 * @name $cacheFactory.Cache 8807 *
8808 * @description 8809 * A cache object used to store and retrieve data, primarily used by 8810 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 8811 * templates and other data. 8812 * 8813 * ```js 8814 * angular.module('superCache') 8815 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 8816 * return $cacheFactory('super-cache'); 8817 * }]); 8818 * ``` 8819 * 8820 * Example test: 8821 * 8822 * ```js 8823 * it('should behave like a cache', inject(function(superCache) { 8824 * superCache.put('key', 'value'); 8825 * superCache.put('another key', 'another value'); 8826 * 8827 * expect(superCache.info()).toEqual({ 8828 * id: 'super-cache', 8829 * size: 2 8830 * }); 8831 * 8832 * superCache.remove('another key'); 8833 * expect(superCache.get('another key')).toBeUndefined(); 8834 * 8835 * superCache.removeAll(); 8836 * expect(superCache.info()).toEqual({ 8837 * id: 'super-cache', 8838 * size: 0 8839 * }); 8840 * })); 8841 * ``` 8842 */ 8843 return caches[cacheId] = { 8844 8845 /** 8846 * @ngdoc method 8847 * @name $cacheFactory.Cache#put 8848 * @kind function 8849 * 8850 * @description 8851 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 8852 * retrieved later, and incrementing the size of the cache if the key was not already 8853 * present in the cache. If behaving like an LRU cache, it will also remove stale 8854 * entries from the set. 8855 * 8856 * It will not insert undefined values into the cache. 8857 * 8858 * @param {string} key the key under which the cached data is stored. 8859 * @param {*} value the value to store alongside the key. If it is undefined, the key 8860 * will not be stored. 8861 * @returns {*} the value stored. 8862 */ 8863 put: function(key, value) { 8864 if (isUndefined(value)) return; 8865 if (capacity < Number.MAX_VALUE) { 8866 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 8867 8868 refresh(lruEntry); 8869 } 8870 8871 if (!(key in data)) size++; 8872 data[key] = value; 8873 8874 if (size > capacity) { 8875 this.remove(staleEnd.key); 8876 } 8877 8878 return value; 8879 }, 8880 8881 /** 8882 * @ngdoc method 8883 * @name $cacheFactory.Cache#get 8884 * @kind function 8885 * 8886 * @description 8887 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 8888 * 8889 * @param {string} key the key of the data to be retrieved 8890 * @returns {*} the value stored. 8891 */ 8892 get: function(key) { 8893 if (capacity < Number.MAX_VALUE) { 8894 var lruEntry = lruHash[key]; 8895 8896 if (!lruEntry) return; 8897 8898 refresh(lruEntry); 8899 } 8900 8901 return data[key]; 8902 }, 8903 8904 8905 /** 8906 * @ngdoc method 8907 * @name $cacheFactory.Cache#remove 8908 * @kind function 8909 * 8910 * @description 8911 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 8912 * 8913 * @param {string} key the key of the entry to be removed 8914 */ 8915 remove: function(key) { 8916 if (capacity < Number.MAX_VALUE) { 8917 var lruEntry = lruHash[key]; 8918 8919 if (!lruEntry) return; 8920 8921 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 8922 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 8923 link(lruEntry.n,lruEntry.p); 8924 8925 delete lruHash[key]; 8926 } 8927 8928 if (!(key in data)) return; 8929 8930 delete data[key]; 8931 size--; 8932 }, 8933 8934 8935 /** 8936 * @ngdoc method 8937 * @name $cacheFactory.Cache#removeAll 8938 * @kind function 8939 * 8940 * @description 8941 * Clears the cache object of any entries. 8942 */ 8943 removeAll: function() { 8944 data = createMap(); 8945 size = 0; 8946 lruHash = createMap(); 8947 freshEnd = staleEnd = null; 8948 }, 8949 8950 8951 /** 8952 * @ngdoc method 8953 * @name $cacheFactory.Cache#destroy 8954 * @kind function 8955 *
8956 * @description 8957 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 8958 * removing it from the {@link $cacheFactory $cacheFactory} set. 8959 */ 8960 destroy: function() { 8961 data = null; 8962 stats = null; 8963 lruHash = null; 8964 delete caches[cacheId]; 8965 }, 8966 8967 8968 /** 8969 * @ngdoc method 8970 * @name $cacheFactory.Cache#info 8971 * @kind function 8972 * 8973 * @description 8974 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 8975 * 8976 * @returns {object} an object with the following properties: 8977 * <ul> 8978 * <li>**id**: the id of the cache instance</li> 8979 * <li>**size**: the number of entries kept in the cache instance</li> 8980 * <li>**...**: any additional properties from the options object when creating the 8981 * cache.</li> 8982 * </ul> 8983 */ 8984 info: function() { 8985 return extend({}, stats, {size: size}); 8986 } 8987 }; 8988 8989 8990 /** 8991 * makes the `entry` the freshEnd of the LRU linked list 8992 */ 8993 function refresh(entry) { 8994 if (entry != freshEnd) { 8995 if (!staleEnd) { 8996 staleEnd = entry; 8997 } else if (staleEnd == entry) { 8998 staleEnd = entry.n; 8999 } 9000 9001 link(entry.n, entry.p); 9002 link(entry, freshEnd); 9003 freshEnd = entry; 9004 freshEnd.n = null; 9005 } 9006 } 9007 9008 9009 /** 9010 * bidirectionally links two entries of the LRU linked list 9011 */ 9012 function link(nextEntry, prevEntry) { 9013 if (nextEntry != prevEntry) { 9014 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 9015 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 9016 } 9017 } 9018 } 9019 9020 9021 /** 9022 * @ngdoc method 9023 * @name $cacheFactory#info 9024 * 9025 * @description 9026 * Get information about all the caches that have been created 9027 * 9028 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 9029 */ 9030 cacheFactory.info = function() { 9031 var info = {}; 9032 forEach(caches, function(cache, cacheId) { 9033 info[cacheId] = cache.info(); 9034 }); 9035 return info; 9036 }; 9037 9038 9039 /** 9040 * @ngdoc method 9041 * @name $cacheFactory#get 9042 * 9043 * @description 9044 * Get access to a cache object by the `cacheId` used when it was created. 9045 * 9046 * @param {string} cacheId Name or id of a cache to access. 9047 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 9048 */ 9049 cacheFactory.get = function(cacheId) { 9050 return caches[cacheId]; 9051 }; 9052 9053 9054 return cacheFactory; 9055 }; 9056} 9057 9058/** 9059 * @ngdoc service 9060 * @name $templateCache 9061 * 9062 * @description 9063 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 9064 * can load templates directly into the cache in a `script` tag, or by consuming the 9065 * `$templateCache` service directly. 9066 * 9067 * Adding via the `script` tag: 9068 * 9069 * ```html 9070 * <script type="text/ng-template" id="templateId.html"> 9071 * <p>This is the content of the template</p> 9072 * </script> 9073 * ``` 9074 * 9075 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 9076 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 9077 * element with ng-app attribute), otherwise the template will be ignored. 9078 * 9079 * Adding via the `$templateCache` service: 9080 * 9081 * ```js 9082 * var myApp = angular.module('myApp', []); 9083 * myApp.run(function($templateCache) { 9084 * $templateCache.put('templateId.html', 'This is the content of the template'); 9085 * }); 9086 * ``` 9087 * 9088 * To retrieve the template later, simply use it in your HTML: 9089 * ```html 9090 * <div ng-include=" 'templateId.html' "></div> 9091 * ``` 9092 * 9093 * or get it via Javascript: 9094 * ```js 9095 * $templateCache.get('templateId.html') 9096 * ``` 9097 * 9098 * See {@link ng.$cacheFactory $cacheFactory}. 9099 * 9100 */ 9101function $TemplateCacheProvider() { 9102 this.$get = ['$cacheFactory', function($cacheFactory) { 9103 return $cacheFactory('templates'); 9104 }]; 9105} 9106 9107/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 9108 * Any commits to this file should be reviewed with security in mind. * 9109 * Changes to this file can potentially create security vulnerabilities. * 9110 * An approval from 2 Core members with history of modifying * 9111 * this file is required. * 9112 * * 9113 * Does the change somehow allow for arbitrary javascript to be executed? * 9114 * Or allows for someone to change the prototype of built-in objects? * 9115 * Or gives undesired access to variables likes document or window? * 9116 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 9117
9118/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 9119 * 9120 * DOM-related variables: 9121 * 9122 * - "node" - DOM Node 9123 * - "element" - DOM Element or Node 9124 * - "$node" or "$element" - jqLite-wrapped node or element 9125 * 9126 * 9127 * Compiler related stuff: 9128 * 9129 * - "linkFn" - linking fn of a single directive 9130 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 9131 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 9132 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 9133 */ 9134 9135 9136/** 9137 * @ngdoc service 9138 * @name $compile 9139 * @kind function 9140 * 9141 * @description 9142 * Compiles an HTML string or DOM into a template and produces a template function, which 9143 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 9144 * 9145 * The compilation is a process of walking the DOM tree and matching DOM elements to 9146 * {@link ng.$compileProvider#directive directives}. 9147 * 9148 * <div class="alert alert-warning"> 9149 * **Note:** This document is an in-depth reference of all directive options. 9150 * For a gentle introduction to directives with examples of common use cases, 9151 * see the {@link guide/directive directive guide}. 9152 * </div> 9153 * 9154 * ## Comprehensive Directive API 9155 * 9156 * There are many different options for a directive. 9157 * 9158 * The difference resides in the return value of the factory function. 9159 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 9160 * or just the `postLink` function (all other properties will have the default values). 9161 * 9162 * <div class="alert alert-success"> 9163 * **Best Practice:** It's recommended to use the "directive definition object" form. 9164 * </div> 9165 * 9166 * Here's an example directive declared with a Directive Definition Object: 9167 * 9168 * ```js 9169 * var myModule = angular.module(...); 9170 * 9171 * myModule.directive('directiveName', function factory(injectables) { 9172 * var directiveDefinitionObject = { 9173 * priority: 0, 9174 * template: '<div></div>
9174', // or // function(tElement, tAttrs) { ... }, 9175 * // or 9176 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 9177 * transclude: false, 9178 * restrict: 'A', 9179 * templateNamespace: 'html', 9180 * scope: false, 9181 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 9182 * controllerAs: 'stringIdentifier', 9183 * bindToController: false, 9184 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 9185 * compile: function compile(tElement, tAttrs, transclude) { 9186 * return { 9187 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 9188 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 9189 * } 9190 * // or 9191 * // return function postLink( ... ) { ... } 9192 * }, 9193 * // or 9194 * // link: { 9195 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 9196 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 9197 * // } 9198 * // or 9199 * // link: function postLink( ... ) { ... } 9200 * }; 9201 * return directiveDefinitionObject; 9202 * }); 9203 * ``` 9204 * 9205 * <div class="alert alert-warning"> 9206 * **Note:** Any unspecified options will use the default value. You can see the default values below. 9207 * </div> 9208 * 9209 * Therefore the above can be simplified as: 9210 * 9211 * ```js 9212 * var myModule = angular.module(...); 9213 * 9214 * myModule.directive('directiveName', function factory(injectables) { 9215 * var directiveDefinitionObject = { 9216 * link: function postLink(scope, iElement, iAttrs) { ... } 9217 * }; 9218 * return directiveDefinitionObject; 9219 * // or 9220 * // return function postLink(scope, iElement, iAttrs) { ... } 9221 * }); 9222 * ``` 9223 * 9224 * 9225 * 9226 * ### Directive Definition Object 9227 * 9228 * The directive definition object provides instructions to the {@link ng.$compile 9229 * compiler}. The attributes are: 9230 * 9231 * #### `multiElement` 9232 * When this property is set to true, the HTML compiler will collect DOM nodes between 9233 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 9234 * together as the directive elements. It is recommended that this feature be used on directives 9235 * which are not strictly behavioral (such as {@link ngClick}), and which 9236 * do not manipulate or replace child nodes (such as {@link ngInclude}). 9237 * 9238 * #### `priority` 9239 * When there are multiple directives defined on a single DOM element, sometimes it 9240 * is necessary to specify the order in which the directives are applied. The `priority` is used 9241 * to sort the directives before their `compile` functions get called. Priority is defined as a 9242 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 9243 * are also run in priority order, but post-link functions are run in reverse order. The order 9244 * of directives with the same priority is undefined. The default priority is `0`. 9245 * 9246 * #### `terminal` 9247 * If set to true then the current `priority` will be the last set of directives 9248 * which will execute (any directives at the current priority will still execute 9249 * as the order of execution on same `priority` is undefined). Note that expressions 9250 * and other directives used in the directive's template will also be excluded from execution. 9251 * 9252 * #### `scope` 9253 * The scope property can be `true`, an object or a falsy value: 9254 * 9255 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. 9256 * 9257 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 9258 * the directive's element. If multiple directives on the same element request a new scope, 9259 * only one new scope is created. The new scope rule does not apply for the root of the template 9260 * since the root of the template always gets a new scope. 9261 * 9262 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The 9263 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent 9264 * scope. This is useful when creating reusable components, which should not accidentally read or modify 9265 * data in the parent scope. 9266 * 9267 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 9268 * directive's element. These local properties are useful for aliasing values for templates. The keys in 9269 * the object hash map to the name of the property on the isolate scope; the values define how the property 9270 * is bound to the parent scope, via matching attributes on the directive's element: 9271 * 9272 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
9273 * always a string since DOM attributes are strings. If no `attr` name is specified then the 9274 * attribute name is assumed to be the same as the local name. Given `<my-component 9275 * my-attr="hello {{name}}">` and the isolate scope definition `scope: { localName:'@myAttr' }`, 9276 * the directive's scope property `localName` will reflect the interpolated value of `hello 9277 * {{name}}`. As the `name` attribute changes so will the `localName` property on the directive's 9278 * scope. The `name` is read from the parent scope (not the directive's scope). 9279 * 9280 * * `=` or `=attr` - set up a bidirectional binding between a local scope property and an expression 9281 * passed via the attribute `attr`. The expression is evaluated in the context of the parent scope. 9282 * If no `attr` name is specified then the attribute name is assumed to be the same as the local 9283 * name. Given `<my-component my-attr="parentModel">` and the isolate scope definition `scope: { 9284 * localModel: '=myAttr' }`, the property `localModel` on the directive's scope will reflect the 9285 * value of `parentModel` on the parent scope. Changes to `parentModel` will be reflected in 9286 * `localModel` and vice versa. Optional attributes should be marked as such with a question mark: 9287 * `=?` or `=?attr`. If the binding expression is non-assignable, or if the attribute isn't 9288 * optional and doesn't exist, an exception ({@link error/$compile/nonassign `$compile:nonassign`}) 9289 * will be thrown upon discovering changes to the local value, since it will be impossible to sync 9290 * them back to the parent scope. By default, the {@link ng.$rootScope.Scope#$watch `$watch`} 9291 * method is used for tracking changes, and the equality check is based on object identity. 9292 * However, if an object literal or an array literal is passed as the binding expression, the 9293 * equality check is done by value (using the {@link angular.equals} function). It's also possible 9294 * to watch the evaluated value shallowly with {@link ng.$rootScope.Scope#$watchCollection 9295 * `$watchCollection`}: use `=*` or `=*attr` (`=*?` or `=*?attr` if the attribute is optional). 9296 * 9297 * * `<` or `<attr` - set up a one-way (one-directional) binding between a local scope property and an 9298 * expression passed via the attribute `attr`. The expression is evaluated in the context of the 9299 * parent scope. If no `attr` name is specified then the attribute name is assumed to be the same as the 9300 * local name. You can also make the binding optional by adding `?`: `<?` or `<?attr`. 9301 * 9302 * For example, given `<my-component my-attr="parentModel">` and directive definition of 9303 * `scope: { localModel:'<myAttr' }`, then the isolated scope property `localModel` will reflect the 9304 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 9305 * in `localModel`, but changes in `localModel` will not reflect in `parentModel`. There are however 9306 * two caveats: 9307 * 1. one-way binding does not copy the value from the parent to the isolate scope, it simply 9308 * sets the same value. That means if your bound value is an object, changes to its properties 9309 * in the isolated scope will be reflected in the parent scope (because both reference the same object). 9310 * 2. one-way binding watches changes to the **identity** of the parent value. That means the 9311 * {@link ng.$rootScope.Scope#$watch `$watch`} on the parent value only fires if the reference 9312 * to the value has changed. In most cases, this should not be of concern, but can be important 9313 * to know if you one-way bind to an object, and then replace that object in the isolated scope. 9314 * If you now change a property of the object in your parent scope, the change will not be 9315 * propagated to the isolated scope, because the identity of the object on the parent scope 9316 * has not changed. Instead you must assign a new object. 9317 * 9318 * One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings 9319 * back to the parent. However, it does not make this completely impossible. 9320 * 9321 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If 9322 * no `attr` name is specified then the attribute name is assumed to be the same as the local name. 9323 * Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: { 9324 * localFn:'&myAttr' }`, the isolate scope property `localFn` will point to a function wrapper for 9325 * the `count = count + value` expression. Often it's desirable to pass data from the isolated scope 9326 * via an expression to the parent scope. This can be done by passing a map of local variable names 9327 * and values into the expression wrapper fn. For example, if the expression is `increment(amount)` 9328 * then we can specify the amount value by calling the `localFn` as `localFn({amount: 22})`. 9329 * 9330 * In general it's possible to apply more than one directive to one element, but there might be limitations 9331 * depending on the type of scope required by the directives. The following points will help explain these limitations. 9332 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 9333 * 9334 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 9335 * * **child scope** + **no scope** => Both directives will share one single child scope 9336 * * **child scope** + **child scope** => Both directives will share one single child scope 9337 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use 9338 * its parent's scope 9339 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 9340 * be applied to the same element. 9341 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 9342 * cannot be applied to the same element. 9343 * 9344 * 9345 * #### `bindToController` 9346 * This property is used to bind scope properties directly to the controller. It can be either 9347 * `true` or an object hash with the same format as the `scope` property. Additionally, a controller 9348 * alias must be set, either by using `controllerAs: 'myAlias'` or by specifying the alias in the controller 9349 * definition: `controller: 'myCtrl as myAlias'`. 9350 * 9351 * When an isolate scope is used for a directive (see above), `bindToController: true` will 9352 * allow a component to have its properties bound to the controller, rather than to scope. 9353 * 9354 * After the controller is instantiated, the initial values of the isolate scope bindings will be bound to the controller 9355 * properties. You can access these bindings once they have been initialized by providing a controller method called 9356 * `$onInit`, which is called after all the controllers on an element have been constructed and had their bindings 9357 * initialized. 9358 * 9359 * <div class="alert alert-warning"> 9360 * **Deprecation warning:** although bindings for non-ES6 class controllers are currently
9361 * bound to `this` before the controller constructor is called, this use is now deprecated. Please place initialization 9362 * code that relies upon bindings inside a `$onInit` method on the controller, instead. 9363 * </div> 9364 * 9365 * It is also possible to set `bindToController` to an object hash with the same format as the `scope` property. 9366 * This will set up the scope bindings to the controller directly. Note that `scope` can still be used 9367 * to define which kind of scope is created. By default, no scope is created. Use `scope: {}` to create an isolate 9368 * scope (useful for component directives). 9369 * 9370 * If both `bindToController` and `scope` are defined and have object hashes, `bindToController` overrides `scope`. 9371 * 9372 * 9373 * #### `controller` 9374 * Controller constructor function. The controller is instantiated before the 9375 * pre-linking phase and can be accessed by other directives (see 9376 * `require` attribute). This allows the directives to communicate with each other and augment 9377 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 9378 * 9379 * * `$scope` - Current scope associated with the element 9380 * * `$element` - Current element 9381 * * `$attrs` - Current attributes object for the element 9382 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 9383 * `function([scope], cloneLinkingFn, futureParentElement, slotName)`: 9384 * * `scope`: (optional) override the scope. 9385 * * `cloneLinkingFn`: (optional) argument to create clones of the original transcluded content. 9386 * * `futureParentElement` (optional): 9387 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 9388 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 9389 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 9390 * and when the `cloneLinkinFn` is passed, 9391 * as those elements need to created and cloned in a special way when they are defined outside their 9392 * usual containers (e.g. like `<svg>`). 9393 * * See also the `directive.templateNamespace` property. 9394 * * `slotName`: (optional) the name of the slot to transclude. If falsy (e.g. `null`, `undefined` or `''`) 9395 * then the default translusion is provided. 9396 * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns 9397 * `true` if the specified slot contains content (i.e. one or more DOM nodes). 9398 * 9399 * The controller can provide the following methods that act as life-cycle hooks: 9400 * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and 9401 * had their bindings initialized (and before the pre & post linking functions for the directives on 9402 * this element). This is a good place to put initialization code for your controller. 9403 * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The 9404 * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an 9405 * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a 9406 * component such as cloning the bound value to prevent accidental mutation of the outer value. 9407 * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing 9408 * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in 9409 * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent 9410 * components will have their `$onDestroy()` hook called before child components. 9411 * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link 9412 * function this hook can be used to set up DOM event handlers and do direct DOM manipulation. 9413 * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since 9414 * they are waiting for their template to load asynchronously and their own compilation and linking has been 9415 * suspended until that occurs. 9416 * 9417 * 9418 * #### `require` 9419 * Require another directive and inject its controller as the fourth argument to the linking function. The 9420 * `require` property can be a string, an array or an object: 9421 * * a **string** containing the name of the directive to pass to the linking function 9422 * * an **array** containing the names of directives to pass to the linking function. The argument passed to the 9423 * linking function will be an array of controllers in the same order as the names in the `require` property 9424 * * an **object** whose property values are the names of the directives to pass to the linking function. The argument 9425 * passed to the linking function will also be an object with matching keys, whose values will hold the corresponding 9426 * controllers. 9427 * 9428 * If the `require` property is an object and `bindToController` is truthy, then the required controllers are 9429 * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers 9430 * have been constructed but before `$onInit` is called. 9431 * See the {@link $compileProvider#component} helper for an example of how this can be used. 9432 * 9433 * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is 9434 * raised (unless no link function is specified and the required controllers are not being bound to the directive 9435 * controller, in which case error checking is skipped). The name can be prefixed with: 9436 * 9437 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 9438 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 9439 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 9440 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 9441 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 9442 * `null` to the `link` fn if not found. 9443 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 9444 * `null` to the `link` fn if not found. 9445 * 9446 * 9447 * #### `controllerAs` 9448 * Identifier name for a reference to the controller in the directive's scope. 9449 * This allows the controller to be referenced from the directive template. This is especially 9450 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 9451 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 9452 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 9453 * 9454 * 9455 * #### `restrict` 9456 * String of subset of `EACM` which restricts the directive to a specific directive 9457 * declaration style. If omitted, the defaults (elements and attributes) are used. 9458 * 9459 * * `E` - Element name (default): `<my-directive></my-directive>` 9460 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 9461 * * `C` - Class: `<div class="my-directive: exp;"></div>` 9462 * * `M` - Comment: `<!-- directive: my-directive exp -->` 9463 * 9464 * 9465 * #### `templateNamespace` 9466 * String representing the document type used by the markup in the template. 9467 * AngularJS needs this information as those elements need to be created and cloned 9468 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 9469 * 9470 * * `html` - All root nodes in the template are HTML. Root nodes may also be 9471 * top-level elements such as `<svg>` or `<math>`. 9472 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 9473 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 9474 * 9475 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 9476 * 9477 * #### `template` 9478 * HTML markup that may: 9479 * * Replace the contents of the directive's element (default). 9480 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 9481 * * Wrap the contents of the directive's element (if `transclude` is true). 9482 * 9483 * Value may be: 9484 * 9485 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 9486 * * A function which takes two arguments `tElement` and `tAttrs` (described
9486in the `compile` 9487 * function api below) and returns a string value. 9488 * 9489 * 9490 * #### `templateUrl` 9491 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 9492 * 9493 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 9494 * for later when the template has been resolved. In the meantime it will continue to compile and link 9495 * sibling and parent elements as though this element had not contained any directives. 9496 * 9497 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 9498 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 9499 * case when only one deeply nested directive has `templateUrl`. 9500 * 9501 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 9502 * 9503 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 9504 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 9505 * a string value representing the url. In either case, the template URL is passed through {@link 9506 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 9507 * 9508 * 9509 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 9510 * specify what the template should replace. Defaults to `false`. 9511 * 9512 * * `true` - the template will replace the directive's element. 9513 * * `false` - the template will replace the contents of the directive's element. 9514 * 9515 * The replacement process migrates all of the attributes / classes from the old element to the new 9516 * one. See the {@link guide/directive#template-expanding-directive 9517 * Directives Guide} for an example. 9518 * 9519 * There are very few scenarios where element replacement is required for the application function, 9520 * the main one being reusable custom components that are used within SVG contexts 9521 * (because SVG doesn't work with custom elements in the DOM tree). 9522 * 9523 * #### `transclude` 9524 * Extract the contents of the element where the directive appears and make it available to the directive. 9525 * The contents are compiled and provided to the directive as a **transclusion function**. See the 9526 * {@link $compile#transclusion Transclusion} section below. 9527 * 9528 * 9529 * #### `compile` 9530 * 9531 * ```js 9532 * function compile(tElement, tAttrs, transclude) { ... } 9533 * ``` 9534 * 9535 * The compile function deals with transforming the template DOM. Since most directives do not do 9536 * template transformation, it is not used often. The compile function takes the following arguments: 9537 * 9538 * * `tElement` - template element - The element where the directive has been declared. It is 9539 * safe to do template transformation on the element and child elements only. 9540 * 9541 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 9542 * between all directive compile functions. 9543 * 9544 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 9545 * 9546 * <div class="alert alert-warning"> 9547 * **Note:** The template instance and the link instance may be different objects if the template has 9548 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 9549 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 9550 * should be done in a linking function rather than in a compile function. 9551 * </div> 9552 9553 * <div class="alert alert-warning"> 9554 * **Note:** The compile function cannot handle directives that recursively use themselves in their 9555 * own templates or compile functions. Compiling these directives results in an infinite loop and 9556 * stack overflow errors. 9557 * 9558 * This can be avoided by manually using $compile in the postLink function to imperatively compile 9559 * a directive's template instead of relying on automatic template compilation via `template` or 9560 * `templateUrl` declaration or manual compilation inside the compile function. 9561 * </div> 9562 * 9563 * <div class="alert alert-danger"> 9564 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 9565 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 9566 * to the link function instead. 9567 * </div> 9568 9569 * A compile function can have a return value which can be either a function or an object. 9570 * 9571 * * returning a (post-link) function - is equivalent to registering the linking function via the 9572 * `link` property of the config object when the compile function is empty. 9573 * 9574 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 9575 * control when a linking function should be called during the linking phase. See info about 9576 * pre-linking and post-linking functions below. 9577 * 9578 * 9579 * #### `link` 9580 * This property is used only if the `compile` property is not defined. 9581 * 9582 * ```js 9583 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 9584 * ``` 9585 * 9586 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 9587 * executed after the template has been cloned. This is where most of the directive logic will be 9588 * put. 9589 * 9590 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 9591 * directive for registering {@link ng.$rootScope.Scope#$watch w
9591atches}. 9592 * 9593 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 9594 * manipulate the children of the element only in `postLink` function since the children have 9595 * already been linked. 9596 * 9597 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 9598 * between all directive linking functions. 9599 * 9600 * * `controller` - the directive's required controller instance(s) - Instances are shared 9601 * among all directives, which allows the directives to use the controllers as a communication 9602 * channel. The exact value depends on the directive's `require` property: 9603 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 9604 * * `string`: the controller instance 9605 * * `array`: array of controller instances 9606 * 9607 * If a required controller cannot be found, and it is optional, the instance is `null`, 9608 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 9609 * 9610 * Note that you can also require the directive's own controller - it will be made available like 9611 * any other controller. 9612 * 9613 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 9614 * This is the same as the `$transclude` 9615 * parameter of directive controllers, see there for details. 9616 * `function([scope], cloneLinkingFn, futureParentElement)`. 9617 * 9618 * #### Pre-linking function 9619 * 9620 * Executed before the child elements are linked. Not safe to do DOM transformation since the 9621 * compiler linking function will fail to locate the correct elements for linking. 9622 * 9623 * #### Post-linking function 9624 * 9625 * Executed after the child elements are linked. 9626 *
9627 * Note that child elements that contain `templateUrl` directives will not have been compiled 9628 * and linked since they are waiting for their template to load asynchronously and their own 9629 * compilation and linking has been suspended until that occurs. 9630 * 9631 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 9632 * for their async templates to be resolved. 9633 * 9634 * 9635 * ### Transclusion 9636 * 9637 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 9638 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 9639 * scope from where they were taken. 9640 * 9641 * Transclusion is used (often with {@link ngTransclude}) to insert the 9642 * original contents of a directive's element into a specified place in the template of the directive. 9643 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 9644 * content has access to the properties on the scope from which it was taken, even if the directive 9645 * has isolated scope. 9646 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 9647 * 9648 * This makes it possible for the widget to have private state for its template, while the transcluded 9649 * content has access to its originating scope. 9650 * 9651 * <div class="alert alert-warning"> 9652 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 9653 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 9654 * Testing Transclusion Directives}. 9655 * </div> 9656 * 9657 * There are three kinds of transclusion depending upon whether you want to transclude just the contents of the 9658 * directive's element, the entire element or multiple parts of the element contents: 9659 * 9660 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 9661 * * `'element'` - transclude the whole of the directive's element including any directives on this 9662 * element that defined at a lower priority than this directive. When used, the `template` 9663 * property is ignored. 9664 * * **`{...}` (an object hash):** - map elements of the content onto transclusion "slots" in the template. 9665 * 9666 * **Mult-slot transclusion** is declared by providing an object for the `transclude` property. 9667 * 9668 * This object is a map where the keys are the name of the slot to fill and the value is an element selector 9669 * used to match the HTML to the slot. The element selector should be in normalized form (e.g. `myElement`) 9670 * and will match the standard element variants (e.g. `my-element`, `my:element`, `data-my-element`, etc). 9671 * 9672 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 9673 * 9674 * If the element selector is prefixed with a `?` then that slot is optional. 9675 * 9676 * For example, the transclude object `{ slotA: '?myCustomElement' }` maps `<my-custom-element>` elements to 9677 * the `slotA` slot, which can be accessed via the `$transclude` function or via the {@link ngTransclude} directive. 9678 * 9679 * Slots that are not marked as optional (`?`) will trigger a compile time error if there are no matching elements 9680 * in the transclude content. If you wish to know if an optional slot was filled with content, then you can call 9681 * `$transclude.isSlotFilled(slotName)` on the transclude function passed to the directive's link function and 9682 * injectable into the directive's controller. 9683 * 9684 * 9685 * #### Transclusion Functions 9686 * 9687 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 9688 * function** to the directive's `link` function and `controller`. This transclusion function is a special 9689 * **linking function** that will return the compiled contents linked to a new transclusion scope. 9690 * 9691 * <div class="alert alert-info"> 9692 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 9693 * ngTransclude will deal with it for us. 9694 * </div> 9695 * 9696 * If you want to manually control the insertion and removal of the transcluded content in your directive 9697 * then you must use this transclude function. When you call a transclude function it returns a a jq
9697Lite/JQuery 9698 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 9699 * 9700 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 9701 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 9702 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 9703 * 9704 * <div class="alert alert-info"> 9705 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function 9706 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 9707 * </div> 9708 * 9709 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 9710 * attach function**: 9711 * 9712 * ```js 9713 * var transcludedContent, transclusionScope; 9714 * 9715 * $transclude(function(clone, scope) { 9716 * element.append(clone); 9717 * transcludedContent = clone; 9718 * transclusionScope = scope; 9719 * }); 9720 * ``` 9721 * 9722 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 9723 * associated transclusion scope: 9724 * 9725 * ```js 9726 * transcludedContent.remove(); 9727 * transclusionScope.$destroy(); 9728 * ``` 9729 * 9730 * <div class="alert alert-info"> 9731 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 9732 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 9733 * then you are also responsible for calling `$destroy` on the transclusion scope. 9734 * </div> 9735 * 9736 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 9737 * automatically destroy their transcluded clones as necessary so you do not need to worry about this if 9738 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 9739 * 9740 * 9741 * #### Transclusion Scopes 9742 * 9743 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 9744 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 9745 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 9746 * was taken. 9747 * 9748 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 9749 * like this: 9750 * 9751 * ```html 9752 * <div ng-app> 9753 * <div isolate> 9754 * <div transclusion> 9755 * </div> 9756 * </div> 9757 * </div> 9758 * ``` 9759 * 9760 * The `$parent` scope hierarchy will look like this: 9761 * 9762 ``` 9763 - $rootScope 9764 - isolate 9765 - transclusion 9766 ``` 9767 * 9768 * but the scopes will inherit prototypically from different scopes to their `$parent`. 9769 * 9770 ``` 9771 - $rootScope 9772 - transclusion 9773 - isolate 9774 ``` 9775 * 9776 * 9777 * ### Attributes 9778 * 9779 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 9780 * `link()` or `compile()` functions. It has a variety of uses. 9781 * 9782 * * *Accessing normalized attribute names:* Directives like 'ngBind' can be expressed in many ways: 9783 * 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. The attributes object allows for normalized access 9784 * to the attributes. 9785 * 9786 * * *Directive inter-communication:* All directives share the same instance of the attributes 9787 * object which allows the directives to use the attributes object as inter directive 9788 * communication. 9789 * 9790 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 9791 * allowing other directives to read the interpolated value. 9792 * 9793 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 9794 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 9795 * the only way to easily get the actual value because during the linking phase the interpolation 9796 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 9797 * 9798 * ```js 9799 * function linkingFn(scope, elm, attrs, ctrl) { 9800 * // get the attribute value 9801 * console.log(attrs.ngModel); 9802 * 9803 * // change the attribute 9804 * attrs.$set('ngModel', 'new value'); 9805 * 9806 * // observe changes to interpolated attribute 9807 * attrs.$observe('ngModel', function(value) { 9808 * console.log('ngModel has changed value to ' + value); 9809 * }); 9810 * } 9811 * ``` 9812 * 9813 * ## Example 9814 * 9815 * <div class="alert alert-warning"> 9816 * **Note**: Typically directives are registered with `module.directive`. The example below is 9817 * to illustrate how `$compile` works. 9818 * </div> 9819 * 9820 <example module="compileExample"> 9821 <file name="index.html"> 9822 <script>
9823 angular.module('compileExample', [], function($compileProvider) { 9824 // configure new 'compile' directive by passing a directive 9825 // factory function. The factory function injects the '$compile' 9826 $compileProvider.directive('compile', function($compile) { 9827 // directive factory creates a link function 9828 return function(scope, element, attrs) { 9829 scope.$watch( 9830 function(scope) { 9831 // watch the 'compile' expression for changes 9832 return scope.$eval(attrs.compile); 9833 }, 9834 function(value) { 9835 // when the 'compile' expression changes 9836 // assign it into the current DOM 9837 element.html(value); 9838 9839 // compile the new DOM and link it to the current 9840 // scope. 9841 // NOTE: we only compile .childNodes so that 9842 // we don't get into infinite loop compiling ourselves 9843 $compile(element.contents())(scope); 9844 } 9845 ); 9846 }; 9847 }); 9848 }) 9849 .controller('GreeterController', ['$scope', function($scope) { 9850 $scope.name = 'Angular'; 9851 $scope.html = 'Hello {{name}}'; 9852 }]); 9853 </script> 9854 <div ng-controller="GreeterController"> 9855 <input ng-model="name"> <br/> 9856 <textarea ng-model="html"></textarea> <br/> 9857 <div compile="html"></div> 9858 </div> 9859 </file> 9860 <file name="protractor.js" type="protractor"> 9861 it('should auto compile', function() { 9862 var textarea = $('textarea'); 9863 var output = $('div[compile]'); 9864 // The initial state reads 'Hello Angular'. 9865 expect(output.getText()).toBe('Hello Angular'); 9866 textarea.clear(); 9867 textarea.sendKeys('{{name}}!'); 9868 expect(output.getText()).toBe('Angular!'); 9869 }); 9870 </file> 9871 </example> 9872 9873 * 9874 * 9875 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 9876 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 9877 * 9878 * <div class="alert alert-danger"> 9879 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 9880 * e.g. will not use the right outer scope. Please pass the transclude function as a 9881 * `parentBoundTranscludeFn` to the link function instead. 9882 * </div> 9883 * 9884 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 9885 * root element(s), not their children) 9886 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 9887 * (a DOM element/tree) to a scope. Where: 9888 * 9889 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 9890 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 9891 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 9892 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 9893 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 9894 * 9895 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 9896 * * `scope` - is the current scope with which the linking function is working with. 9897 * 9898 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 9899 * keys may be used to control linking behavior: 9900 * 9901 * * `parentBoundTranscludeFn` - the transclude function made available to 9902 * directives; if given, it will be passed through to the link functions of 9903 * directives found in `element` during compilation. 9904 * * `transcludeControllers` - an object hash with keys that map controller names 9905 * to a hash with the key `instance`, which maps to the controller instance; 9906 * if given, it will make the controllers available to directives on the compileNode: 9907 * ``` 9908 * { 9909 * parent: { 9910 * instance: parentControllerInstance 9911 * } 9912 * } 9913 * ``` 9914 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 9915 * the cloned elements; only needed for transcludes that are allowed to contain non html 9916 * elements (e.g. SVG elements). See also the directive.controller property. 9917 * 9918 * Calling the linking function returns the element of the template. It is either the original 9919 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 9920 * 9921 * After linking the view is not updated until after a call to $digest which typically is done by 9922 * Angular automatically. 9923 * 9924 * If you need access to the bound view, there are two ways to do it: 9925 * 9926 * - If you are not asking the linking function to clone the template, create the DOM element(s) 9927 * before you send them to the compiler and keep this reference around. 9928 * ```js 9929 * var element = $compile('<p>{{total}}</p>')(scope); 9930 * ``` 9931 * 9932 * - if on the other hand, you need the element to be cloned, the view reference from the original 9933 * example would not point to the clone, but rather to the original template that was cloned. In 9934 * this case, you can access the clone via the cloneAttachFn: 9935 * ```js 9936 * var templateElement = angular.element('<p>{{total}}</p>'), 9937 * scope = ....; 9938 * 9939 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 9940 * //attach the clone to DOM document at the right place 9941 * }); 9942 * 9943 * //now we have reference to the cloned DOM via `clonedElement` 9944 * ``` 9945 * 9946 * 9947 * For information on how the compiler works, see the 9948 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 9949 */ 9950 9951var $compileMinErr = minErr('$compile'); 9952 9953function UNINITIALIZED_VALUE() {} 9954var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE(); 9955 9956/** 9957 * @ngdoc provider 9958 * @name $compileProvider 9959 * 9960 * @description 9961 */
vendor: 16,166 bytes, lines 9962-10350
9962$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 9963function $CompileProvider($provide, $$sanitizeUriProvider) { 9964 var hasDirectives = {}, 9965 Suffix = 'Directive', 9966 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 9967 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 9968 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 9969 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 9970 9971 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 9972 // The assumption is that future DOM event attribute names will begin with 9973 // 'on' and be composed of only English letters. 9974 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 9975 var bindingCache = createMap(); 9976 9977 function parseIsolateBindings(scope, directiveName, isController) { 9978 var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/; 9979 9980 var bindings = createMap(); 9981 9982 forEach(scope, function(definition, scopeName) { 9983 if (definition in bindingCache) { 9984 bindings[scopeName] = bindingCache[definition]; 9985 return; 9986 } 9987 var match = definition.match(LOCAL_REGEXP); 9988 9989 if (!match) { 9990 throw $compileMinErr('iscp', 9991 "Invalid {3} for directive '{0}'." + 9992 " Definition: {... {1}: '{2}' ...}", 9993 directiveName, scopeName, definition, 9994 (isController ? "controller bindings definition" : 9995 "isolate scope definition")); 9996 } 9997 9998 bindings[scopeName] = { 9999 mode: match[1][0], 10000 collection: match[2] === '*', 10001 optional: match[3] === '?', 10002 attrName: match[4] || scopeName 10003 }; 10004 if (match[4]) { 10005 bindingCache[definition] = bindings[scopeName]; 10006 } 10007 }); 10008 10009 return bindings; 10010 } 10011 10012 function parseDirectiveBindings(directive, directiveName) { 10013 var bindings = { 10014 isolateScope: null, 10015 bindToController: null 10016 }; 10017 if (isObject(directive.scope)) { 10018 if (directive.bindToController === true) { 10019 bindings.bindToController = parseIsolateBindings(directive.scope, 10020 directiveName, true); 10021 bindings.isolateScope = {}; 10022 } else { 10023 bindings.isolateScope = parseIsolateBindings(directive.scope, 10024 directiveName, false); 10025 } 10026 } 10027 if (isObject(directive.bindToController)) { 10028 bindings.bindToController = 10029 parseIsolateBindings(directive.bindToController, directiveName, true); 10030 } 10031 if (isObject(bindings.bindToController)) { 10032 var controller = directive.controller; 10033 var controllerAs = directive.controllerAs; 10034 if (!controller) { 10035 // There is no controller, there may or may not be a controllerAs property 10036 throw $compileMinErr('noctrl', 10037 "Cannot bind to controller without directive '{0}'s controller.", 10038 directiveName); 10039 } else if (!identifierForController(controller, controllerAs)) { 10040 // There is a controller, but no identifier or controllerAs property 10041 throw $compileMinErr('noident', 10042 "Cannot bind to controller without identifier for directive '{0}'.", 10043 directiveName); 10044 } 10045 } 10046 return bindings; 10047 } 10048 10049 function assertValidDirectiveName(name) { 10050 var letter = name.charAt(0); 10051 if (!letter || letter !== lowercase(letter)) { 10052 throw $compileMinErr('baddir', "Directive/Component name '{0}' is invalid. The first character must be a lowercase letter", name); 10053 } 10054 if (name !== name.trim()) { 10055 throw $compileMinErr('baddir', 10056 "Directive/Component name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 10057 name); 10058 } 10059 } 10060 10061 /** 10062 * @ngdoc method 10063 * @name $compileProvider#directive 10064 * @kind function 10065 * 10066 * @description 10067 * Register a new directive with the compiler. 10068 * 10069 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 10070 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 10071 * names and the values are the factories. 10072 * @param {Function|Array} directiveFactory An injectable directive factory function. See the 10073 * {@link guide/directive directive guide} and the {@link $compile compile API} for more info. 10074 * @returns {ng.$compileProvider} Self for chaining. 10075 */ 10076 this.directive = function registerDirective(name, directiveFactory) { 10077 assertNotHasOwnProperty(name, 'directive'); 10078 if (isString(name)) { 10079 assertValidDirectiveName(name); 10080 assertArg(directiveFactory, 'directiveFactory'); 10081 if (!hasDirectives.hasOwnProperty(name)) { 10082 hasDirectives[name] = []; 10083 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 10084 function($injector, $exceptionHandler) { 10085 var directives = []; 10086 forEach(hasDirectives[name], function(directiveFactory, index) { 10087 try { 10088 var directive = $injector.invoke(directiveFactory); 10089 if (isFunction(directive)) { 10090 directive = { compile: valueFn(directive) }; 10091 } else if (!directive.compile && directive.link) { 10092 directive.compile = valueFn(directive.link); 10093 } 10094 directive.priority = directive.priority || 0; 10095 directive.index = index; 10096 directive.name = directive.name || name; 10097 directive.require = directive.require || (directive.controller && directive.name); 10098 directive.restrict = directive.restrict || 'EA'; 10099 directive.$$moduleName = directiveFactory.$$moduleName; 10100 directives.push(directive); 10101 } catch (e) { 10102 $exceptionHandler(e); 10103 } 10104 }); 10105 return directives; 10106 }]); 10107 } 10108 hasDirectives[name].push(directiveFactory); 10109 } else { 10110 forEach(name, reverseParams(registerDirective)); 10111 } 10112 return this; 10113 }; 10114 10115 /** 10116 * @ngdoc method 10117 * @name $compileProvider#component 10118 * @module ng 10119 * @param {string} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`) 10120 * @param {Object} options Component definition object (a simplified 10121 * {@link ng.$compile#directive-definition-object directive definition object}), 10122 * with the following properties (all optional): 10123 * 10124 * - `controller` â `{(string|function()=}` â controller constructor function that should be 10125 * associated with newly created scope or the name of a {@link ng.$compile#-controller- 10126 * registered controller} if passed as a string. An empty `noop` function by default. 10127 * - `controllerAs` â `{string=}` â identifier name for to reference the controller in the component's scope. 10128 * If present, the controller will be published to scope under the `controllerAs` name. 10129 * If not present, this will default to be `$ctrl`. 10130 * - `template` â `{string=|function()=}` â html template as a string or a function that 10131 * returns an html template as a string which should be used as the contents of this component. 10132 * Empty string by default. 10133 * 10134 * If `template` is a function, then it is {@link auto.$injector#invoke injected} with 10135 * the following locals: 10136 * 10137 * - `$element` - Current element 10138 * - `$attrs` - Current attributes object for the element 10139 * 10140 * - `templateUrl` â `{string=|function()=}` â path or function that returns a path to an html 10141 * template that should be used as the contents of this component. 10142 * 10143 * If `templateUrl` is a function, then it is {@link auto.$injector#invoke injected} with 10144 * the following locals: 10145 * 10146 * - `$element` - Current element 10147 * - `$attrs` - Current attributes object for the element 10148 * 10149 * - `bindings` â `{object=}` â defines bindings between DOM attributes and component properties. 10150 * Component properties are always bound to the component controller and not to the scope. 10151 * See {@link ng.$compile#-bindtocontroller- `bindToController`}. 10152 * - `transclude` â `{boolean=}` â whether {@link $compile#transclusion content transclusion} is enabled. 10153 * Disabled by default. 10154 * - `require` - `{Object<string, string>=}` - requires the controllers of other directives and binds them to 10155 * this component's controller. The object keys specify the property names under which the required 10156 * controllers (object values) will be bound. See {@link ng.$compile#-require- `require`}. 10157 * - `$...` â additional properties to attach to the directive factory function and the controller 10158 * constructor function. (This is used by the component router to annotate) 10159 * 10160 * @returns {ng.$compileProvider} the compile provider itself, for chaining of function calls. 10161 * @description 10162 * Register a **component definition** with the compiler. This is a shorthand for registering a special 10163 * type of directive, which represents a self-contained UI component in your application. Such components 10164 * are always isolated (i.e. `scope: {}`) and are always restricted to elements (i.e. `restrict: 'E'`). 10165 * 10166 * Component definitions are very simple and do not require as much configuration as defining general 10167 * directives. Component definitions usually consist only of a template and a controller backing it. 10168 * 10169 * In order to make the definition easier, components enforce best practices like use of `controllerAs`, 10170 * `bindToController`. They always have **isolate scope** and are restricted to elements. 10171 * 10172 * Here are a few examples of how you would usually define components: 10173 * 10174 * ```js 10175 * var myMod = angular.module(...); 10176 * myMod.component('myComp', { 10177 * template: '<div>My name is {{$ctrl.name}}</div>', 10178 * controller: function() { 10179 * this.name = 'shahar'; 10180 * } 10181 * }); 10182 * 10183 * myMod.component('myComp', { 10184 * template: '<div>My name is {{$ctrl.name}}</div>', 10185 * bindings: {name: '@'} 10186 * }); 10187 * 10188 * myMod.component('myComp', { 10189 * templateUrl: 'views/my-comp.html', 10190 * controller: 'MyCtrl', 10191 * controllerAs: 'ctrl', 10192 * bindings: {name: '@'} 10193 * }); 10194 * 10195 * ``` 10196 * For more examples, and an in-depth guide, see the {@link guide/component component guide}. 10197 * 10198 * <br /> 10199 * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. 10200 */ 10201 this.component = function registerComponent(name, options) { 10202 var controller = options.controller || function() {}; 10203 10204 function factory($injector) { 10205 function makeInjectable(fn) { 10206 if (isFunction(fn) || isArray(fn)) { 10207 return function(tElement, tAttrs) { 10208 return $injector.invoke(fn, this, {$element: tElement, $attrs: tAttrs}); 10209 }; 10210 } else { 10211 return fn; 10212 } 10213 } 10214 10215 var template = (!options.template && !options.templateUrl ? '' : options.template); 10216 var ddo = { 10217 controller: controller, 10218 controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', 10219 template: makeInjectable(template), 10220 templateUrl: makeInjectable(options.templateUrl), 10221 transclude: options.transclude, 10222 scope: {}, 10223 bindToController: options.bindings || {}, 10224 restrict: 'E', 10225 require: options.require 10226 }; 10227 10228 // Copy annotations (starting with $) over to the DDO 10229 forEach(options, function(val, key) { 10230 if (key.charAt(0) === '$') ddo[key] = val; 10231 }); 10232 10233 return ddo; 10234 } 10235 10236 // TODO(pete) remove the following `forEach` before we release 1.6.0 10237 // The [email protected] looks for the annotations on the controller constructor 10238 // Nothing in Angular looks for annotations on the factory function but we can't remove 10239 // it from 1.5.x yet. 10240 10241 // Copy any annotation properties (starting with $) over to the factory and controller constructor functions 10242 // These could be used by libraries such as the new component router 10243 forEach(options, function(val, key) { 10244 if (key.charAt(0) === '$') { 10245 factory[key] = val; 10246 // Don't try to copy over annotations to named controller 10247 if (isFunction(controller)) controller[key] = val; 10248 } 10249 }); 10250 10251 factory.$inject = ['$injector']; 10252 10253 return this.directive(name, factory); 10254 }; 10255 10256 10257 /** 10258 * @ngdoc method 10259 * @name $compileProvider#aHrefSanitizationWhitelist 10260 * @kind function 10261 * 10262 * @description 10263 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 10264 * urls during a[href] sanitization. 10265 * 10266 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 10267 * 10268 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 10269 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 10270 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 10271 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 10272 * 10273 * @param {RegExp=} regexp New regexp to whitelist urls with. 10274 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 10275 * chaining otherwise. 10276 */ 10277 this.aHrefSanitizationWhitelist = function(regexp) { 10278 if (isDefined(regexp)) { 10279 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 10280 return this; 10281 } else { 10282 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 10283 } 10284 }; 10285 10286 10287 /** 10288 * @ngdoc method 10289 * @name $compileProvider#imgSrcSanitizationWhitelist 10290 * @kind function 10291 * 10292 * @description 10293 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 10294 * urls during img[src] sanitization. 10295 * 10296 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 10297 * 10298 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 10299 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 10300 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 10301 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 10302 * 10303 * @param {RegExp=} regexp New regexp to whitelist urls with. 10304 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 10305 * chaining otherwise. 10306 */ 10307 this.imgSrcSanitizationWhitelist = function(regexp) { 10308 if (isDefined(regexp)) { 10309 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 10310 return this; 10311 } else { 10312 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 10313 } 10314 }; 10315 10316 /** 10317 * @ngdoc method 10318 * @name $compileProvider#debugInfoEnabled 10319 * 10320 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 10321 * current debugInfoEnabled state 10322 * @returns {*} current value if used as getter or itself (chaining) if used as setter 10323 * 10324 * @kind function 10325 * 10326 * @description 10327 * Call this method to enable/disable various debug runtime information in the compiler such as adding 10328 * binding information and a reference to the current scope on to DOM elements. 10329 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 10330 * * `ng-binding` CSS class 10331 * * `$binding` data property containing an array of the binding expressions 10332 * 10333 * You may want to disable this in production for a significant performance boost. See 10334 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 10335 * 10336 * The default value is true. 10337 */ 10338 var debugInfoEnabled = true; 10339 this.debugInfoEnabled = function(enabled) { 10340 if (isDefined(enabled)) { 10341 debugInfoEnabled = enabled; 10342 return this; 10343 } 10344 return debugInfoEnabled; 10345 }; 10346 10347 10348 var TTL = 10; 10349 /** 10350 * @ngdoc method
10351 * @name $compileProvider#onChangesTtl 10352 * @description 10353 * 10354 * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and 10355 * assuming that the model is unstable. 10356 * 10357 * The current default is 10 iterations. 10358 * 10359 * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result 10360 * in several iterations of calls to these hooks. However if an application needs more than the default 10 10361 * iterations to stabilize then you should investigate what is causing the model to continuously change during 10362 * the `$onChanges` hook execution. 10363 * 10364 * Increasing the TTL could have performance implications, so you should not change it without proper justification. 10365 * 10366 * @param {number} limit The number of `$onChanges` hook iterations. 10367 * @returns {number|object} the current limit (or `this` if called as a setter for chaining) 10368 */ 10369 this.onChangesTtl = function(value) { 10370 if (arguments.length) { 10371 TTL = value; 10372 return this; 10373 } 10374 return TTL; 10375 }; 10376 10377 this.$get = [ 10378 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 10379 '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', 10380 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 10381 $controller, $rootScope, $sce, $animate, $$sanitizeUri) { 10382 10383 var SIMPLE_ATTR_NAME = /^\w/; 10384 var specialAttrHolder = window.document.createElement('div'); 10385 10386 10387 10388 var onChangesTtl = TTL; 10389 // The onChanges hooks should all be run together in a single digest 10390 // When changes occur, the call to trigger their hooks will be added to this queue 10391 var onChangesQueue; 10392 10393 // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest 10394 function flushOnChangesQueue() { 10395 try { 10396 if (!(--onChangesTtl)) { 10397 // We have hit the TTL limit so reset everything 10398 onChangesQueue = undefined; 10399 throw $compileMinErr('infchng', '{0} $onChanges() iterations reached. Aborting!\n', TTL); 10400 } 10401 // We must run this hook in an apply since the $$postDigest runs outside apply 10402 $rootScope.$apply(function() { 10403 for (var i = 0, ii = onChangesQueue.length; i < ii; ++i) { 10404 onChangesQueue[i](); 10405 } 10406 // Reset the queue to trigger a new schedule next time there is a change 10407 onChangesQueue = undefined; 10408 }); 10409 } finally { 10410 onChangesTtl++; 10411 } 10412 } 10413 10414 10415 function Attributes(element, attributesToCopy) { 10416 if (attributesToCopy) { 10417 var keys = Object.keys(attributesToCopy); 10418 var i, l, key; 10419 10420 for (i = 0, l = keys.length; i < l; i++) { 10421 key = keys[i]; 10422 this[key] = attributesToCopy[key]; 10423 } 10424 } else { 10425 this.$attr = {}; 10426 } 10427 10428 this.$$element = element; 10429 } 10430 10431 Attributes.prototype = { 10432 /** 10433 * @ngdoc method 10434 * @name $compile.directive.Attributes#$normalize 10435 * @kind function 10436 * 10437 * @description 10438 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 10439 * `data-`) to its normalized, camelCase form. 10440 * 10441 * Also there is special case for Moz prefix starting with upper case letter. 10442 * 10443 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 10444 * 10445 * @param {string} name Name to normalize 10446 */ 10447 $normalize: directiveNormalize, 10448 10449 10450 /** 10451 * @ngdoc method 10452 * @name $compile.directive.Attributes#$addClass 10453 * @kind function 10454 * 10455 * @description 10456 * Adds the CSS class value specified by the classVal parameter to the element. If animations 10457 * are enabled then an animation will be triggered for the class addition. 10458 * 10459 * @param {string} classVal The className value that will be added to the element 10460 */ 10461 $addClass: function(classVal) { 10462 if (classVal && classVal.length > 0) { 10463 $animate.addClass(this.$$element, classVal); 10464 } 10465 }, 10466 10467 /** 10468 * @ngdoc method 10469 * @name $compile.directive.Attributes#$removeClass 10470 * @kind function 10471 *
10472 * @description 10473 * Removes the CSS class value specified by the classVal parameter from the element. If 10474 * animations are enabled then an animation will be triggered for the class removal. 10475 * 10476 * @param {string} classVal The className value that will be removed from the element 10477 */ 10478 $removeClass: function(classVal) { 10479 if (classVal && classVal.length > 0) { 10480 $animate.removeClass(this.$$element, classVal); 10481 } 10482 }, 10483 10484 /** 10485 * @ngdoc method 10486 * @name $compile.directive.Attributes#$updateClass 10487 * @kind function 10488 * 10489 * @description 10490 * Adds and removes the appropriate CSS class values to the element based on the difference 10491 * between the new and old CSS class values (specified as newClasses and oldClasses). 10492 * 10493 * @param {string} newClasses The current CSS className value 10494 * @param {string} oldClasses The former CSS className value 10495 */ 10496 $updateClass: function(newClasses, oldClasses) { 10497 var toAdd = tokenDifference(newClasses, oldClasses); 10498 if (toAdd && toAdd.length) { 10499 $animate.addClass(this.$$element, toAdd); 10500 } 10501 10502 var toRemove = tokenDifference(oldClasses, newClasses); 10503 if (toRemove && toRemove.length) { 10504 $animate.removeClass(this.$$element, toRemove); 10505 } 10506 }, 10507 10508 /** 10509 * Set a normalized attribute on the element in a way such that all directives 10510 * can share the attribute. This function properly handles boolean attributes. 10511 * @param {string} key Normalized key. (ie ngAttribute) 10512 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 10513 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 10514 * Defaults to true. 10515 * @param {string=} attrName Optional none normalized name. Defaults to key. 10516 */ 10517 $set: function(key, value, writeAttr, attrName) { 10518 // TODO: decide whether or not to throw an error if "class" 10519 //is set through this function since it may cause $updateClass to 10520 //become unstable. 10521 10522 var node = this.$$element[0], 10523 booleanKey = getBooleanAttrName(node, key), 10524 aliasedKey = getAliasedAttrName(key), 10525 observer = key, 10526 nodeName; 10527 10528 if (booleanKey) { 10529 this.$$element.prop(key, value); 10530 attrName = booleanKey; 10531 } else if (aliasedKey) { 10532 this[aliasedKey] = value; 10533 observer = aliasedKey; 10534 } 10535 10536 this[key] = value; 10537 10538 // translate normalized key to actual key 10539 if (attrName) { 10540 this.$attr[key] = attrName; 10541 } else { 10542 attrName = this.$attr[key]; 10543 if (!attrName) { 10544 this.$attr[key] = attrName = snake_case(key, '-'); 10545 } 10546 } 10547 10548 nodeName = nodeName_(this.$$element); 10549 10550 if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || 10551 (nodeName === 'img' && key === 'src')) { 10552 // sanitize a[href] and img[src] values 10553 this[key] = value = $$sanitizeUri(value, key === 'src'); 10554 } else if (nodeName === 'img' && key === 'srcset') { 10555 // sanitize img[srcset] values 10556 var result = ""; 10557 10558 // first check if there are spaces because it's not the same pattern 10559 var trimmedSrcset = trim(value); 10560 // ( 999x ,| 999w ,| ,|, ) 10561 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 10562 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 10563 10564 // split srcset into tuple of uri and descriptor except for the last item 10565 var rawUris = trimmedSrcset.split(pattern); 10566 10567 // for each tuples 10568 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 10569 for (var i = 0; i < nbrUrisWith2parts; i++) { 10570 var innerIdx = i * 2; 10571 // sanitize the uri 10572 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 10573 // add the descriptor 10574 result += (" " + trim(rawUris[innerIdx + 1])); 10575 } 10576 10577 // split the last item into uri and descriptor 10578 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 10579 10580 // sanitize the last uri 10581 result += $$sanitizeUri(trim(lastTuple[0]), true); 10582 10583 // and add the last descriptor if any 10584 if (lastTuple.length === 2) { 10585 result += (" " + trim(lastTuple[1])); 10586 } 10587 this[key] = value = result; 10588 } 10589 10590 if (writeAttr !== false) { 10591 if (value === null || isUndefined(value)) { 10592 this.$$element.removeAttr(attrName); 10593 } else { 10594 if (SIMPLE_ATTR_NAME.test(attrName)) { 10595 this.$$element.attr(attrName, value); 10596 } else { 10597 setSpecialAttr(this.$$element[0], attrName, value); 10598 } 10599 } 10600 } 10601 10602 // fire observers 10603 var $$observers = this.$$observers;
vendor: 15,637 bytes, lines 10604-10984
10604 $$observers && forEach($$observers[observer], function(fn) { 10605 try { 10606 fn(value); 10607 } catch (e) { 10608 $exceptionHandler(e); 10609 } 10610 }); 10611 }, 10612 10613 10614 /** 10615 * @ngdoc method 10616 * @name $compile.directive.Attributes#$observe 10617 * @kind function 10618 * 10619 * @description 10620 * Observes an interpolated attribute. 10621 * 10622 * The observer function will be invoked once during the next `$digest` following 10623 * compilation. The observer is then invoked whenever the interpolated value 10624 * changes. 10625 * 10626 * @param {string} key Normalized key. (ie ngAttribute) . 10627 * @param {function(interpolatedValue)} fn Function that will be called whenever 10628 the interpolated value of the attribute changes. 10629 * See the {@link guide/interpolation#how-text-and-attribute-bindings-work Interpolation 10630 * guide} for more info. 10631 * @returns {function()} Returns a deregistration function for this observer. 10632 */ 10633 $observe: function(key, fn) { 10634 var attrs = this, 10635 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 10636 listeners = ($$observers[key] || ($$observers[key] = [])); 10637 10638 listeners.push(fn); 10639 $rootScope.$evalAsync(function() { 10640 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 10641 // no one registered attribute interpolation function, so lets call it manually 10642 fn(attrs[key]); 10643 } 10644 }); 10645 10646 return function() { 10647 arrayRemove(listeners, fn); 10648 }; 10649 } 10650 }; 10651 10652 function setSpecialAttr(element, attrName, value) { 10653 // Attributes names that do not start with letters (such as `(click)`) cannot be set using `setAttribute` 10654 // so we have to jump through some hoops to get such an attribute 10655 // https://github.com/angular/angular.js/pull/13318 10656 specialAttrHolder.innerHTML = "<span " + attrName + ">"; 10657 var attributes = specialAttrHolder.firstChild.attributes; 10658 var attribute = attributes[0]; 10659 // We have to remove the attribute from its container element before we can add it to the destination element 10660 attributes.removeNamedItem(attribute.name); 10661 attribute.value = value; 10662 element.attributes.setNamedItem(attribute); 10663 } 10664 10665 function safeAddClass($element, className) { 10666 try { 10667 $element.addClass(className); 10668 } catch (e) { 10669 // ignore, since it means that we are trying to set class on 10670 // SVG element, where class name is read-only. 10671 } 10672 } 10673 10674 10675 var startSymbol = $interpolate.startSymbol(), 10676 endSymbol = $interpolate.endSymbol(), 10677 denormalizeTemplate = (startSymbol == '{{' && endSymbol == '}}') 10678 ? identity 10679 : function denormalizeTemplate(template) { 10680 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 10681 }, 10682 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 10683 var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/; 10684 10685 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 10686 var bindings = $element.data('$binding') || []; 10687 10688 if (isArray(binding)) { 10689 bindings = bindings.concat(binding); 10690 } else { 10691 bindings.push(binding); 10692 } 10693 10694 $element.data('$binding', bindings); 10695 } : noop; 10696 10697 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 10698 safeAddClass($element, 'ng-binding'); 10699 } : noop; 10700 10701 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 10702 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 10703 $element.data(dataName, scope); 10704 } : noop; 10705 10706 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 10707 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 10708 } : noop; 10709 10710 compile.$$createComment = function(directiveName, comment) { 10711 var content = ''; 10712 if (debugInfoEnabled) { 10713 content = ' ' + (directiveName || '') + ': ' + (comment || '') + ' '; 10714 } 10715 return window.document.createComment(content); 10716 }; 10717 10718 return compile; 10719 10720 //================================ 10721 10722 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 10723 previousCompileContext) { 10724 if (!($compileNodes instanceof jqLite)) { 10725 // jquery always rewraps, whereas we need to preserve the original selector so that we can 10726 // modify it. 10727 $compileNodes = jqLite($compileNodes); 10728 } 10729 10730 var NOT_EMPTY = /\S+/; 10731 10732 // We can not compile top level text elements since text nodes can be merged and we will 10733 // not be able to attach scope data to them, so we will wrap them in <span> 10734 for (var i = 0, len = $compileNodes.length; i < len; i++) { 10735 var domNode = $compileNodes[i]; 10736 10737 if (domNode.nodeType === NODE_TYPE_TEXT && domNode.nodeValue.match(NOT_EMPTY) /* non-empty */) { 10738 jqLiteWrapNode(domNode, $compileNodes[i] = window.document.createElement('span')); 10739 } 10740 } 10741 10742 var compositeLinkFn = 10743 compileNodes($compileNodes, transcludeFn, $compileNodes, 10744 maxPriority, ignoreDirective, previousCompileContext); 10745 compile.$$addScopeClass($compileNodes); 10746 var namespace = null; 10747 return function publicLinkFn(scope, cloneConnectFn, options) { 10748 assertArg(scope, 'scope'); 10749 10750 if (previousCompileContext && previousCompileContext.needsNewScope) { 10751 // A parent directive did a replace and a directive on this element asked 10752 // for transclusion, which caused us to lose a layer of element on which 10753 // we could hold the new transclusion scope, so we will create it manually 10754 // here. 10755 scope = scope.$parent.$new(); 10756 } 10757 10758 options = options || {}; 10759 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 10760 transcludeControllers = options.transcludeControllers, 10761 futureParentElement = options.futureParentElement; 10762 10763 // When `parentBoundTranscludeFn` is passed, it is a 10764 // `controllersBoundTransclude` function (it was previously passed 10765 // as `transclude` to directive.link) so we must unwrap it to get 10766 // its `boundTranscludeFn` 10767 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 10768 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 10769 } 10770 10771 if (!namespace) { 10772 namespace = detectNamespaceForChildElements(futureParentElement); 10773 } 10774 var $linkNode; 10775 if (namespace !== 'html') { 10776 // When using a directive with replace:true and templateUrl the $compileNodes 10777 // (or a child element inside of them) 10778 // might change, so we need to recreate the namespace adapted compileNodes 10779 // for call to the link function. 10780 // Note: This will already clone the nodes... 10781 $linkNode = jqLite( 10782 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 10783 ); 10784 } else if (cloneConnectFn) { 10785 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 10786 // and sometimes changes the structure of the DOM. 10787 $linkNode = JQLitePrototype.clone.call($compileNodes); 10788 } else { 10789 $linkNode = $compileNodes; 10790 } 10791 10792 if (transcludeControllers) { 10793 for (var controllerName in transcludeControllers) { 10794 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 10795 } 10796 } 10797 10798 compile.$$addScopeInfo($linkNode, scope); 10799 10800 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 10801 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 10802 return $linkNode; 10803 }; 10804 } 10805 10806 function detectNamespaceForChildElements(parentElement) { 10807 // TODO: Make this detect MathML as well... 10808 var node = parentElement && parentElement[0]; 10809 if (!node) { 10810 return 'html'; 10811 } else { 10812 return nodeName_(node) !== 'foreignobject' && toString.call(node).match(/SVG/) ? 'svg' : 'html'; 10813 } 10814 } 10815 10816 /** 10817 * Compile function matches each node in nodeList against the directives. Once all directives 10818 * for a particular node are collected their compile functions are executed. The compile 10819 * functions return values - the linking functions - are combined into a composite linking 10820 * function, which is the a linking function for the node. 10821 * 10822 * @param {NodeList} nodeList an array of nodes or NodeList to compile 10823 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 10824 * scope argument is auto-generated to the new child of the transcluded parent scope. 10825 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 10826 * the rootElement must be set the jqLite collection of the compile root. This is 10827 * needed so that the jqLite collection items can be replaced with widgets. 10828 * @param {number=} maxPriority Max directive priority. 10829 * @returns {Function} A composite linking function of all of the matched directives or null. 10830 */ 10831 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 10832 previousCompileContext) { 10833 var linkFns = [], 10834 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 10835 10836 for (var i = 0; i < nodeList.length; i++) { 10837 attrs = new Attributes(); 10838 10839 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 10840 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 10841 ignoreDirective); 10842 10843 nodeLinkFn = (directives.length) 10844 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 10845 null, [], [], previousCompileContext) 10846 : null; 10847 10848 if (nodeLinkFn && nodeLinkFn.scope) { 10849 compile.$$addScopeClass(attrs.$$element); 10850 } 10851 10852 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 10853 !(childNodes = nodeList[i].childNodes) || 10854 !childNodes.length) 10855 ? null 10856 : compileNodes(childNodes, 10857 nodeLinkFn ? ( 10858 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 10859 && nodeLinkFn.transclude) : transcludeFn); 10860 10861 if (nodeLinkFn || childLinkFn) { 10862 linkFns.push(i, nodeLinkFn, childLinkFn); 10863 linkFnFound = true; 10864 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 10865 } 10866 10867 //use the previous context only for the first element in the virtual group 10868 previousCompileContext = null; 10869 } 10870 10871 // return a linking function if we have found anything, null otherwise 10872 return linkFnFound ? compositeLinkFn : null; 10873 10874 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 10875 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 10876 var stableNodeList; 10877 10878 10879 if (nodeLinkFnFound) { 10880 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 10881 // offsets don't get screwed up 10882 var nodeListLength = nodeList.length; 10883 stableNodeList = new Array(nodeListLength); 10884 10885 // create a sparse array by only copying the elements which have a linkFn 10886 for (i = 0; i < linkFns.length; i+=3) { 10887 idx = linkFns[i]; 10888 stableNodeList[idx] = nodeList[idx]; 10889 } 10890 } else { 10891 stableNodeList = nodeList; 10892 } 10893 10894 for (i = 0, ii = linkFns.length; i < ii;) { 10895 node = stableNodeList[linkFns[i++]]; 10896 nodeLinkFn = linkFns[i++]; 10897 childLinkFn = linkFns[i++]; 10898 10899 if (nodeLinkFn) { 10900 if (nodeLinkFn.scope) { 10901 childScope = scope.$new(); 10902 compile.$$addScopeInfo(jqLite(node), childScope); 10903 } else { 10904 childScope = scope; 10905 } 10906 10907 if (nodeLinkFn.transcludeOnThisElement) { 10908 childBoundTranscludeFn = createBoundTranscludeFn( 10909 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 10910 10911 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 10912 childBoundTranscludeFn = parentBoundTranscludeFn; 10913 10914 } else if (!parentBoundTranscludeFn && transcludeFn) { 10915 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 10916 10917 } else { 10918 childBoundTranscludeFn = null; 10919 } 10920 10921 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 10922 10923 } else if (childLinkFn) { 10924 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 10925 } 10926 } 10927 } 10928 } 10929 10930 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 10931 function boundTranscludeFn(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 10932 10933 if (!transcludedScope) { 10934 transcludedScope = scope.$new(false, containingScope); 10935 transcludedScope.$$transcluded = true; 10936 } 10937 10938 return transcludeFn(transcludedScope, cloneFn, { 10939 parentBoundTranscludeFn: previousBoundTranscludeFn, 10940 transcludeControllers: controllers, 10941 futureParentElement: futureParentElement 10942 }); 10943 } 10944 10945 // We need to attach the transclusion slots onto the `boundTranscludeFn` 10946 // so that they are available inside the `controllersBoundTransclude` function 10947 var boundSlots = boundTranscludeFn.$$slots = createMap(); 10948 for (var slotName in transcludeFn.$$slots) { 10949 if (transcludeFn.$$slots[slotName]) { 10950 boundSlots[slotName] = createBoundTranscludeFn(scope, transcludeFn.$$slots[slotName], previousBoundTranscludeFn); 10951 } else { 10952 boundSlots[slotName] = null; 10953 } 10954 } 10955 10956 return boundTranscludeFn; 10957 } 10958 10959 /** 10960 * Looks for directives on the given node and adds them to the directive collection which is 10961 * sorted. 10962 * 10963 * @param node Node to search. 10964 * @param directives An array to which the directives are added to. This array is sorted before 10965 * the function returns. 10966 * @param attrs The shared attrs object which is used to populate the normalized attributes. 10967 * @param {number=} maxPriority Max directive priority. 10968 */ 10969 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 10970 var nodeType = node.nodeType, 10971 attrsMap = attrs.$attr, 10972 match, 10973 className; 10974 10975 switch (nodeType) { 10976 case NODE_TYPE_ELEMENT: /* Element */ 10977 // use the node name: <directive> 10978 addDirective(directives, 10979 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 10980 10981 // iterate over the attributes 10982 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 10983 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 10984 var attrStartName = false;
10985 var attrEndName = false; 10986 10987 attr = nAttrs[j]; 10988 name = attr.name; 10989 value = trim(attr.value); 10990 10991 // support ngAttr attribute binding 10992 ngAttrName = directiveNormalize(name); 10993 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 10994 name = name.replace(PREFIX_REGEXP, '') 10995 .substr(8).replace(/_(.)/g, function(match, letter) { 10996 return letter.toUpperCase(); 10997 }); 10998 } 10999 11000 var multiElementMatch = ngAttrName.match(MULTI_ELEMENT_DIR_RE); 11001 if (multiElementMatch && directiveIsMultiElement(multiElementMatch[1])) { 11002 attrStartName = name; 11003 attrEndName = name.substr(0, name.length - 5) + 'end'; 11004 name = name.substr(0, name.length - 6); 11005 } 11006 11007 nName = directiveNormalize(name.toLowerCase()); 11008 attrsMap[nName] = name; 11009 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 11010 attrs[nName] = value; 11011 if (getBooleanAttrName(node, nName)) { 11012 attrs[nName] = true; // presence means true 11013 } 11014 } 11015 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 11016 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 11017 attrEndName); 11018 } 11019 11020 // use class as directive 11021 className = node.className; 11022 if (isObject(className)) { 11023 // Maybe SVGAnimatedString 11024 className = className.animVal; 11025 } 11026 if (isString(className) && className !== '') { 11027 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 11028 nName = directiveNormalize(match[2]); 11029 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 11030 attrs[nName] = trim(match[3]); 11031 } 11032 className = className.substr(match.index + match[0].length); 11033 } 11034 } 11035 break; 11036 case NODE_TYPE_TEXT: /* Text Node */ 11037 if (msie === 11) { 11038 // Workaround for #11781 11039 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 11040 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 11041 node.parentNode.removeChild(node.nextSibling); 11042 } 11043 } 11044 addTextInterpolateDirective(directives, node.nodeValue); 11045 break; 11046 case NODE_TYPE_COMMENT: /* Comment */ 11047 try { 11048 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 11049 if (match) { 11050 nName = directiveNormalize(match[1]); 11051 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 11052 attrs[nName] = trim(match[2]); 11053 } 11054 } 11055 } catch (e) { 11056 // turns out that under some circumstances IE9 throws errors when one attempts to read 11057 // comment's node value. 11058 // Just ignore it and continue. (Can't seem to reproduce in test case.) 11059 } 11060 break; 11061 } 11062 11063 directives.sort(byPriority); 11064 return directives; 11065 } 11066 11067 /** 11068 * Given a node with an directive-start it collects all of the siblings until it finds 11069 * directive-end. 11070 * @param node 11071 * @param attrStart 11072 * @param attrEnd 11073 * @returns {*} 11074 */ 11075 function groupScan(node, attrStart, attrEnd) { 11076 var nodes = []; 11077 var depth = 0; 11078 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 11079 do { 11080 if (!node) { 11081 throw $compileMinErr('uterdir', 11082 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 11083 attrStart, attrEnd); 11084 } 11085 if (node.nodeType == NODE_TYPE_ELEMENT) { 11086 if (node.hasAttribute(attrStart)) depth++; 11087 if (node.hasAttribute(attrEnd)) depth--; 11088 } 11089 nodes.push(node); 11090 node = node.nextSibling; 11091 } while (depth > 0); 11092 } else { 11093 nodes.push(node); 11094 } 11095 11096 return jqLite(nodes); 11097 } 11098 11099 /** 11100 * Wrapper for linking function which converts normal linking function into a grouped 11101 * linking function. 11102 * @param linkFn 11103 * @param attrStart 11104 * @param attrEnd 11105 * @returns {Function} 11106 */ 11107 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 11108 return function groupedElementsLink(scope, element, attrs, controllers, transcludeFn) { 11109 element = groupScan(element[0], attrStart, attrEnd); 11110 return linkFn(scope, element, attrs, controllers, transcludeFn); 11111 }; 11112 } 11113 11114 /** 11115 * A function generator that is used to support both eager and lazy compilation 11116 * linking function. 11117 * @param eager 11118 * @param $compileNodes 11119 * @param transcludeFn 11120 * @param maxPriority 11121 * @param ignoreDirective 11122 * @param previousCompileContext 11123 * @returns {Function} 11124 */ 11125 function compilationGenerator(eager, $compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { 11126 var compiled; 11127 11128 if (eager) { 11129 return compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 11130 } 11131 return function lazyCompilation() { 11132 if (!compiled) { 11133 compiled = compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext); 11134 11135 // Null out all of these references in order to make them eligible for garbage collection 11136 // since this is a potentially long lived closure 11137 $compileNodes = transcludeFn = previousCompileContext = null; 11138 } 11139 return compiled.apply(this, arguments); 11140 }; 11141 } 11142 11143 /** 11144 * Once the directives have been collected, their compile functions are executed. This method 11145 * is responsible for inlining directive templates as well as terminating the application 11146 * of the directives if the terminal directive has been reached. 11147 * 11148 * @param {Array} directives Array of collected directives to execute their compile function. 11149 * this needs to be pre-sorted by priority order. 11150 * @param {Node} compileNode The raw DOM node to apply the compile functions to 11151 * @param {Object} templateAttrs The shared attribute function 11152 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 11153 * scope argument is auto-generated to the new 11154 * child of the transcluded parent scope. 11155 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 11156 * argument has the root jqLite array so that we can replace nodes 11157 * on it. 11158 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 11159 * compiling the transclusion. 11160 * @param {Array.<Function>} preLinkFns 11161 * @param {Array.<Function>} postLinkFns 11162 * @param {Object} previousCompileContext Context used for previous compilation of the current 11163 * node 11164 * @returns {Function} linkFn 11165 */ 11166 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 11167 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 11168 previousCompileContext) { 11169 previousCompileContext = previousCompileContext || {}; 11170 11171 var terminalPriority = -Number.MAX_VALUE, 11172 newScopeDirective = previousCompileContext.newScopeDirective, 11173 controllerDirectives = previousCompileContext.controllerDirectives, 11174 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 11175 templateDirective = previousCompileContext.templateDirective, 11176 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 11177 hasTranscludeDirective = false, 11178 hasTemplate = false, 11179 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 11180 $compileNode = templateAttrs.$$element = jqLite(compileNode), 11181 directive, 11182 directiveName, 11183 $template, 11184 replaceDirective = originalReplaceDirective, 11185 childTranscludeFn = transcludeFn, 11186 linkFn, 11187 didScanForMultipleTransclusion = false, 11188 mightHaveMultipleTransclusionError = false, 11189 directiveValue; 11190 11191 // executes all directives on the current element 11192 for (var i = 0, ii = directives.length; i < ii; i++) { 11193 directive = directives[i]; 11194 var attrStart = directive.$$start; 11195 var attrEnd = directive.$$end; 11196 11197 // collect multiblock sections 11198 if (attrStart) { 11199 $compileNode = groupScan(compileNode, attrStart, attrEnd); 11200 } 11201 $template = undefined; 11202 11203 if (terminalPriority > directive.priority) { 11204 break; // prevent further processing of directives 11205 } 11206 11207 if (directiveValue = directive.scope) { 11208 11209 // skip the check for directives with async templates, we'll check the derived sync 11210 // directive when the template arrives 11211 if (!directive.templateUrl) { 11212 if (isObject(directiveValue)) { 11213 // This directive is trying to add an isolated scope. 11214 // Check that there is no scope of any kind already 11215 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 11216 directive, $compileNode); 11217 newIsolateScopeDirective = directive; 11218 } else { 11219 // This directive is trying to add a child scope. 11220 // Check that there is no isolated scope already 11221 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 11222 $compileNode); 11223 } 11224 } 11225 11226 newScopeDirective = newScopeDirective || directive; 11227 } 11228 11229 directiveName = directive.name; 11230 11231 // If we encounter a condition that can result in transclusion on the directive, 11232 // then scan ahead in the remaining directives for others that may cause a multiple 11233 // transclusion error to be thrown during the compilation process. If a matching directive 11234 // is found, then we know that when we encounter a transcluded directive, we need to eagerly 11235 // compile the `transclude` function rather than doing it lazily in order to throw 11236 // exceptions at the correct time 11237 if (!didScanForMultipleTransclusion && ((directive.replace && (directive.templateUrl || directive.template)) 11238 || (directive.transclude && !directive.$$tlb))) { 11239 var candidateDirective; 11240 11241 for (var scanningIndex = i + 1; candidateDirective = directives[scanningIndex++];) { 11242 if ((candidateDirective.transclude && !candidateDirective.$$tlb) 11243 || (candidateDirective.replace && (candidateDirective.templateUrl || candidateDirective.template))) { 11244 mightHaveMultipleTransclusionError = true; 11245 break; 11246 } 11247 } 11248 11249 didScanForMultipleTransclusion = true; 11250 } 11251 11252 if (!directive.templateUrl && directive.controller) { 11253 directiveValue = directive.controller; 11254 controllerDirectives = controllerDirectives || createMap(); 11255 assertNoDuplicate("'" + directiveName + "' controller", 11256 controllerDirectives[directiveName], directive, $compileNode); 11257 controllerDirectives[directiveName] = directive; 11258 } 11259 11260 if (directiveValue = directive.transclude) { 11261 hasTranscludeDirective = true; 11262 11263 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 11264 // This option should only be used by directives that know how to safely handle element transclusion, 11265 // where the transcluded nodes are added or replaced after linking. 11266 if (!directive.$$tlb) { 11267 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 11268 nonTlbTranscludeDirective = directive; 11269 } 11270 11271 if (directiveValue == 'element') { 11272 hasElementTranscludeDirective = true; 11273 terminalPriority = directive.priority; 11274 $template = $compileNode; 11275 $compileNode = templateAttrs.$$element = 11276 jqLite(compile.$$createComment(directiveName, templateAttrs[directiveName])); 11277 compileNode = $compileNode[0]; 11278 replaceWith(jqCollection, sliceArgs($template), compileNode); 11279
11280 // Support: Chrome < 50 11281 // https://github.com/angular/angular.js/issues/14041 11282 11283 // In the versions of V8 prior to Chrome 50, the document fragment that is created 11284 // in the `replaceWith` function is improperly garbage collected despite still 11285 // being referenced by the `parentNode` property of all of the child nodes. By adding 11286 // a reference to the fragment via a different property, we can avoid that incorrect 11287 // behavior. 11288 // TODO: remove this line after Chrome 50 has been released 11289 $template[0].$$parentNode = $template[0].parentNode; 11290 11291 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, terminalPriority, 11292 replaceDirective && replaceDirective.name, { 11293 // Don't pass in: 11294 // - controllerDirectives - otherwise we'll create duplicates controllers 11295 // - newIsolateScopeDirective or templateDirective - comb
11295ining templates with 11296 // element transclusion doesn't make sense. 11297 // 11298 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 11299 // on the same element more than once. 11300 nonTlbTranscludeDirective: nonTlbTranscludeDirective 11301 }); 11302 } else { 11303 11304 var slots = createMap(); 11305 11306 $template = jqLite(jqLiteClone(compileNode)).contents(); 11307 11308 if (isObject(directiveValue)) { 11309 11310 // We have transclusion slots, 11311 // collect them up, compile them and store their transclusion functions 11312 $template = []; 11313 11314 var slotMap = createMap(); 11315 var filledSlots = createMap(); 11316 11317 // Parse the element selectors 11318 forEach(directiveValue, function(elementSelector, slotName) { 11319 // If an element selector starts with a ? then it is optional 11320 var optional = (elementSelector.charAt(0) === '?'); 11321 elementSelector = optional ? elementSelector.substring(1) : elementSelector; 11322 11323 slotMap[elementSelector] = slotName; 11324 11325 // We explicitly assign `null` since this implies that a slot was defined but not filled. 11326 // Later when calling boundTransclusion functions with a slot name we only error if the 11327 // slot is `undefined` 11328 slots[slotName] = null; 11329 11330 // filledSlots contains `true` for all slots that are either optional or have been 11331 // filled. This is used to check that we have not missed any required slots 11332 filledSlots[slotName] = optional; 11333 }); 11334 11335 // Add the matching elements into their slot 11336 forEach($compileNode.contents(), function(node) { 11337 var slotName = slotMap[directiveNormalize(nodeName_(node))]; 11338 if (slotName) { 11339 filledSlots[slotName] = true; 11340 slots[slotName] = slots[slotName] || []; 11341 slots[slotName].push(node); 11342 } else { 11343 $template.push(node); 11344 } 11345 }); 11346 11347 // Check for required slots that were not filled 11348 forEach(filledSlots, function(filled, slotName) { 11349 if (!filled) { 11350 throw $compileMinErr('reqslot', 'Required transclusion slot `{0}` was not filled.', slotName); 11351 } 11352 }); 11353 11354 for (var slotName in slots) { 11355 if (slots[slotName]) { 11356 // Only define a transclusion function if the slot was filled 11357 slots[slotName] = compilationGenerator(mightHaveMultipleTransclusionError, slots[slotName], transcludeFn); 11358 } 11359 } 11360 } 11361 11362 $compileNode.empty(); // clear contents 11363 childTranscludeFn = compilationGenerator(mightHaveMultipleTransclusionError, $template, transcludeFn, undefined, 11364 undefined, { needsNewScope: directive.$$isolateScope || directive.$$newScope}); 11365 childTranscludeFn.$$slots = slots; 11366 } 11367 } 11368 11369 if (directive.template) { 11370 hasTemplate = true; 11371 assertNoDuplicate('template', templateDirective, directive, $compileNode); 11372 templateDirective = directive; 11373 11374 directiveValue = (isFunction(directive.template)) 11375 ? directive.template($compileNode, templateAttrs) 11376 : directive.template; 11377 11378 directiveValue = denormalizeTemplate(directiveValue); 11379 11380 if (directive.replace) { 11381 replaceDirective = directive; 11382 if (jqLiteIsTextNode(directiveValue)) { 11383 $template = []; 11384 } else { 11385 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 11386 } 11387 compileNode = $template[0]; 11388 11389 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 11390 throw $compileMinErr('tplrt', 11391 "Template for directive '{0}' must have exactly one root element. {1}", 11392 directiveName, ''); 11393 } 11394 11395 replaceWith(jqCollection, $compileNode, compileNode); 11396 11397 var newTemplateAttrs = {$attr: {}}; 11398
vendor: 13,070 bytes, lines 11399-11692
11399 // combine directives from the original node and from the template: 11400 // - take the array of directives for this element 11401 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 11402 // - collect directives from the template and sort them by priority 11403 // - combine directives as: processed + template + unprocessed 11404 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 11405 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 11406 11407 if (newIsolateScopeDirective || newScopeDirective) { 11408 // The original directive caused the current element to be replaced but this element 11409 // also needs to have a new scope, so we need to tell the template directives 11410 // that they would need to get their scope from further up, if they require transclusion 11411 markDirectiveScope(templateDirectives, newIsolateScopeDirective, newScopeDirective); 11412 } 11413 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 11414 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 11415 11416 ii = directives.length; 11417 } else { 11418 $compileNode.html(directiveValue); 11419 } 11420 } 11421 11422 if (directive.templateUrl) { 11423 hasTemplate = true; 11424 assertNoDuplicate('template', templateDirective, directive, $compileNode); 11425 templateDirective = directive; 11426 11427 if (directive.replace) { 11428 replaceDirective = directive; 11429 } 11430 11431 /* jshint -W021 */ 11432 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 11433 /* jshint +W021 */ 11434 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 11435 controllerDirectives: controllerDirectives, 11436 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 11437 newIsolateScopeDirective: newIsolateScopeDirective, 11438 templateDirective: templateDirective, 11439 nonTlbTranscludeDirective: nonTlbTranscludeDirective 11440 }); 11441 ii = directives.length; 11442 } else if (directive.compile) { 11443 try { 11444 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 11445 if (isFunction(linkFn)) { 11446 addLinkFns(null, linkFn, attrStart, attrEnd); 11447 } else if (linkFn) { 11448 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 11449 } 11450 } catch (e) { 11451 $exceptionHandler(e, startingTag($compileNode)); 11452 } 11453 } 11454 11455 if (directive.terminal) { 11456 nodeLinkFn.terminal = true; 11457 terminalPriority = Math.max(terminalPriority, directive.priority); 11458 } 11459 11460 } 11461 11462 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 11463 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 11464 nodeLinkFn.templateOnThisElement = hasTemplate; 11465 nodeLinkFn.transclude = childTranscludeFn; 11466 11467 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 11468 11469 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 11470 return nodeLinkFn; 11471 11472 //////////////////// 11473 11474 function addLinkFns(pre, post, attrStart, attrEnd) { 11475 if (pre) { 11476 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 11477 pre.require = directive.require; 11478 pre.directiveName = directiveName; 11479 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 11480 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 11481 } 11482 preLinkFns.push(pre); 11483 } 11484 if (post) { 11485 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 11486 post.require = directive.require; 11487 post.directiveName = directiveName; 11488 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 11489 post = cloneAndAnnotateFn(post, {isolateScope: true}); 11490 } 11491 postLinkFns.push(post); 11492 } 11493 } 11494 11495 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 11496 var i, ii, linkFn, isolateScope, controllerScope, elementControllers, transcludeFn, $element, 11497 attrs, scopeBindingInfo; 11498 11499 if (compileNode === linkNode) { 11500 attrs = templateAttrs; 11501 $element = templateAttrs.$$element; 11502 } else { 11503 $element = jqLite(linkNode); 11504 attrs = new Attributes($element, templateAttrs); 11505 } 11506 11507 controllerScope = scope; 11508 if (newIsolateScopeDirective) { 11509 isolateScope = scope.$new(true); 11510 } else if (newScopeDirective) { 11511 controllerScope = scope.$parent; 11512 } 11513 11514 if (boundTranscludeFn) { 11515 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 11516 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 11517 transcludeFn = controllersBoundTransclude; 11518 transcludeFn.$$boundTransclude = boundTranscludeFn; 11519 // expose the slots on the `$transclude` function 11520 transcludeFn.isSlotFilled = function(slotName) { 11521 return !!boundTranscludeFn.$$slots[slotName]; 11522 }; 11523 } 11524 11525 if (controllerDirectives) { 11526 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective); 11527 } 11528 11529 if (newIsolateScopeDirective) { 11530 // Initialize isolate scope bindings for new isolate scope directive. 11531 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 11532 templateDirective === newIsolateScopeDirective.$$originalDirective))); 11533 compile.$$addScopeClass($element, true); 11534 isolateScope.$$isolateBindings = 11535 newIsolateScopeDirective.$$isolateBindings; 11536 scopeBindingInfo = initializeDirectiveBindings(scope, attrs, isolateScope, 11537 isolateScope.$$isolateBindings, 11538 newIsolateScopeDirective); 11539 if (scopeBindingInfo.removeWatches) { 11540 isolateScope.$on('$destroy', scopeBindingInfo.removeWatches); 11541 } 11542 } 11543 11544 // Initialize bindToController bindings 11545 for (var name in elementControllers) { 11546 var controllerDirective = controllerDirectives[name]; 11547 var controller = elementControllers[name]; 11548 var bindings = controllerDirective.$$bindings.bindToController; 11549 11550 if (controller.identifier && bindings) { 11551 controller.bindingInfo = 11552 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 11553 } else { 11554 controller.bindingInfo = {}; 11555 } 11556 11557 var controllerResult = controller(); 11558 if (controllerResult !== controller.instance) { 11559 // If the controller constructor has a return value, overwrite the instance 11560 // from setupControllers 11561 controller.instance = controllerResult; 11562 $element.data('$' + controllerDirective.name + 'Controller', controllerResult); 11563 controller.bindingInfo.removeWatches && controller.bindingInfo.removeWatches(); 11564 controller.bindingInfo = 11565 initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); 11566 } 11567 } 11568 11569 // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy 11570 forEach(controllerDirectives, function(controllerDirective, name) { 11571 var require = controllerDirective.require; 11572 if (controllerDirective.bindToController && !isArray(require) && isObject(require)) { 11573 extend(elementControllers[name].instance, getControllers(name, require, $element, elementControllers)); 11574 } 11575 }); 11576 11577 // Handle the init and destroy lifecycle hooks on all controllers that have them 11578 forEach(elementControllers, function(controller) { 11579 var controllerInstance = controller.instance; 11580 if (isFunction(controllerInstance.$onChanges)) { 11581 controllerInstance.$onChanges(controller.bindingInfo.initialChanges); 11582 } 11583 if (isFunction(controllerInstance.$onInit)) { 11584 controllerInstance.$onInit(); 11585 } 11586 if (isFunction(controllerInstance.$onDestroy)) { 11587 controllerScope.$on('$destroy', function callOnDestroyHook() { 11588 controllerInstance.$onDestroy(); 11589 }); 11590 } 11591 }); 11592 11593 // PRELINKING 11594 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 11595 linkFn = preLinkFns[i]; 11596 invokeLinkFn(linkFn, 11597 linkFn.isolateScope ? isolateScope : scope, 11598 $element, 11599 attrs, 11600 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 11601 transcludeFn 11602 ); 11603 } 11604 11605 // RECURSION 11606 // We only pass the isolate scope, if the isolate directive has a template, 11607 // otherwise the child elements do not belong to the isolate directive. 11608 var scopeToChild = scope; 11609 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 11610 scopeToChild = isolateScope; 11611 } 11612 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 11613 11614 // POSTLINKING 11615 for (i = postLinkFns.length - 1; i >= 0; i--) { 11616 linkFn = postLinkFns[i]; 11617 invokeLinkFn(linkFn, 11618 linkFn.isolateScope ? isolateScope : scope, 11619 $element, 11620 attrs, 11621 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 11622 transcludeFn 11623 ); 11624 } 11625 11626 // Trigger $postLink lifecycle hooks 11627 forEach(elementControllers, function(controller) { 11628 var controllerInstance = controller.instance; 11629 if (isFunction(controllerInstance.$postLink)) { 11630 controllerInstance.$postLink(); 11631 } 11632 }); 11633 11634 // This is the function that is injected as `$transclude`. 11635 // Note: all arguments are optional! 11636 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement, slotName) { 11637 var transcludeControllers; 11638 // No scope passed in: 11639 if (!isScope(scope)) { 11640 slotName = futureParentElement; 11641 futureParentElement = cloneAttachFn; 11642 cloneAttachFn = scope; 11643 scope = undefined; 11644 } 11645 11646 if (hasElementTranscludeDirective) { 11647 transcludeControllers = elementControllers; 11648 } 11649 if (!futureParentElement) { 11650 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 11651 } 11652 if (slotName) { 11653 // slotTranscludeFn can be one of three things: 11654 // * a transclude function - a filled slot 11655 // * `null` - an optional slot that was not filled 11656 // * `undefined` - a slot that was not declared (i.e. invalid) 11657 var slotTranscludeFn = boundTranscludeFn.$$slots[slotName]; 11658 if (slotTranscludeFn) { 11659 return slotTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 11660 } else if (isUndefined(slotTranscludeFn)) { 11661 throw $compileMinErr('noslot', 11662 'No parent directive that requires a transclusion with slot name "{0}". ' + 11663 'Element: {1}', 11664 slotName, startingTag($element)); 11665 } 11666 } else { 11667 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 11668 } 11669 } 11670 } 11671 } 11672 11673 function getControllers(directiveName, require, $element, elementControllers) { 11674 var value; 11675 11676 if (isString(require)) { 11677 var match = require.match(REQUIRE_PREFIX_REGEXP); 11678 var name = require.substring(match[0].length); 11679 var inheritType = match[1] || match[3]; 11680 var optional = match[2] === '?'; 11681 11682 //If only parents then start at the parent element 11683 if (inheritType === '^^') { 11684 $element = $element.parent(); 11685 //Otherwise attempt getting the controller from elementControllers in case 11686 //the element is transcluded (and has no data) and to avoid .data if possible 11687 } else { 11688 value = elementControllers && elementControllers[name]; 11689 value = value && value.instance; 11690 } 11691 11692 if (!value) {
11693 var dataName = '$' + name + 'Controller'; 11694 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 11695 } 11696 11697 if (!value && !optional) { 11698 throw $compileMinErr('ctreq', 11699 "Controller '{0}', required by directive '{1}', can't be found!", 11700 name, directiveName); 11701 } 11702 } else if (isArray(require)) { 11703 value = []; 11704 for (var i = 0, ii = require.length; i < ii; i++) { 11705 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 11706 } 11707 } else if (isObject(require)) { 11708 value = {}; 11709 forEach(require, function(controller, property) { 11710 value[property] = getControllers(directiveName, controller, $element, elementControllers); 11711 }); 11712 } 11713 11714 return value || null; 11715 } 11716 11717 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope, newIsolateScopeDirective) { 11718 var elementControllers = createMap(); 11719 for (var controllerKey in controllerDirectives) { 11720 var directive = controllerDirectives[controllerKey]; 11721 var locals = { 11722 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 11723 $element: $element, 11724 $attrs: attrs, 11725 $transclude: transcludeFn 11726 }; 11727 11728 var controller = directive.controller; 11729 if (controller == '@') { 11730 controller = attrs[directive.name]; 11731 } 11732 11733 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 11734 11735 // For directives with element transclusion the element is a comment. 11736 // In this case .data will not attach any data. 11737 // Instead, we save the controllers for the element in a local hash and attach to .data 11738 // later, once we have the actual element. 11739 elementControllers[directive.name] = controllerInstance; 11740 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 11741 } 11742 return elementControllers; 11743 } 11744 11745 // Depending upon the context in which a directive finds itself it might need to have a new isolated 11746 // or child scope created. For instance: 11747 // * if the directive has been pulled into a template because another directive with a higher priority 11748 // asked for element transclusion 11749 // * if the directive itself asks for transclusion but it is at the root of a template and the original 11750 // element was replaced. See https://github.com/angular/angular.js/issues/12936 11751 function markDirectiveScope(directives, isolateScope, newScope) { 11752 for (var j = 0, jj = directives.length; j < jj; j++) { 11753 directives[j] = inherit(directives[j], {$$isolateScope: isolateScope, $$newScope: newScope}); 11754 } 11755 } 11756 11757 /** 11758 * looks up the directive and decorates it with exception handling and proper parameters. We 11759 * call this the boundDirective. 11760 * 11761 * @param {string} name name of the directive to look up. 11762 * @param {string} location The directive must be found in specific format. 11763 * String containing any of theses characters: 11764 * 11765 * * `E`: element name 11766 * * `A': attribute 11767 * * `C`: class 11768 * * `M`: comment 11769 * @returns {boolean} true if directive was added. 11770 */ 11771 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 11772 endAttrName) { 11773 if (name === ignoreDirective) return null; 11774 var match = null; 11775 if (hasDirectives.hasOwnProperty(name)) { 11776 for (var directive, directives = $injector.get(name + Suffix), 11777 i = 0, ii = directives.length; i < ii; i++) { 11778 try { 11779 directive = directives[i]; 11780 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 11781 directive.restrict.indexOf(location) != -1) { 11782 if (startAttrName) { 11783 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 11784 } 11785 if (!directive.$$bindings) { 11786 var bindings = directive.$$bindings = 11787 parseDirectiveBindings(directive, directive.name); 11788 if (isObject(bindings.isolateScope)) { 11789 directive.$$isolateBindings = bindings.isolateScope; 11790 } 11791 } 11792 tDirectives.push(directive); 11793 match = directive; 11794 } 11795 } catch (e) { $exceptionHandler(e); } 11796 } 11797 } 11798 return match; 11799 } 11800 11801 11802 /** 11803 * looks up the directive and returns true if it is a multi-element directive, 11804 * and therefore requires DOM nodes between -start and -end markers to be grouped 11805 * together. 11806 * 11807 * @param {string} name name of the directive to look up. 11808 * @returns true if directive was registered as multi-element. 11809 */ 11810 function directiveIsMultiElement(name) { 11811 if (hasDirectives.hasOwnProperty(name)) { 11812 for (var directive, directives = $injector.get(name + Suffix), 11813 i = 0, ii = directives.length; i < ii; i++) { 11814 directive = directives[i]; 11815 if (directive.multiElement) { 11816 return true; 11817 } 11818 } 11819 } 11820 return false;
11821 } 11822 11823 /** 11824 * When the element is replaced with HTML template then the new attributes 11825 * on the template need to be merged with the existing attributes in the DOM. 11826 * The desired effect is to have both of the attributes present. 11827 * 11828 * @param {object} dst destination attributes (original DOM) 11829 * @param {object} src source attributes (from the directive template) 11830 */ 11831 function mergeTemplateAttributes(dst, src) { 11832 var srcAttr = src.$attr, 11833 dstAttr = dst.$attr, 11834 $element = dst.$$element; 11835 11836 // reapply the old attributes to the new element 11837 forEach(dst, function(value, key) { 11838 if (key.charAt(0) != '$') { 11839 if (src[key] && src[key] !== value) { 11840 value += (key === 'style' ? ';' : ' ') + src[key]; 11841 } 11842 dst.$set(key, value, true, srcAttr[key]); 11843 } 11844 }); 11845 11846 // copy the new attributes on the old attrs object 11847 forEach(src, function(value, key) { 11848 if (key == 'class') { 11849 safeAddClass($element, value); 11850 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 11851 } else if (key == 'style') { 11852 $element.attr('style', $element.attr('style') + ';' + value); 11853 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 11854 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 11855 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 11856 // have an attribute like "has-own-property" or "data-has-own-property", etc. 11857 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 11858 dst[key] = value; 11859 dstAttr[key] = srcAttr[key]; 11860 } 11861 }); 11862 } 11863 11864 11865 function compileTemplateUrl(directives, $compileNode, tAttrs, 11866 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 11867 var linkQueue = [], 11868 afterTemplateNodeLinkFn, 11869 afterTemplateChildLinkFn, 11870 beforeTemplateCompileNode = $compileNode[0], 11871 origAsyncDirective = directives.shift(), 11872 derivedSyncDirective = inherit(origAsyncDirective, { 11873 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 11874 }), 11875 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 11876 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 11877 : origAsyncDirective.templateUrl, 11878 templateNamespace = origAsyncDirective.templateNamespace; 11879 11880 $compileNode.empty(); 11881 11882 $templateRequest(templateUrl) 11883 .then(function(content) { 11884 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 11885 11886 content = denormalizeTemplate(content); 11887 11888 if (origAsyncDirective.replace) { 11889 if (jqLiteIsTextNode(content)) { 11890 $template = []; 11891 } else { 11892 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 11893 } 11894 compileNode = $template[0]; 11895 11896 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 11897 throw $compileMinErr('tplrt', 11898 "Template for directive '{0}' must have exactly one root element. {1}", 11899 origAsyncDirective.name, templateUrl); 11900 } 11901 11902 tempTemplateAttrs = {$attr: {}}; 11903 replaceWith($rootElement, $compileNode, compileNode); 11904 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 11905 11906 if (isObject(origAsyncDirective.scope)) { 11907 // the original directive that caused the template to be loaded async required 11908 // an isolate scope 11909 markDirectiveScope(templateDirectives, true); 11910 } 11911 directives = templateDirectives.concat(directives); 11912 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 11913 } else { 11914 compileNode = beforeTemplateCompileNode; 11915 $compileNode.html(content); 11916 } 11917 11918 directives.unshift(derivedSyncDirective); 11919 11920 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 11921 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 11922 previousCompileContext);
vendor: 11,992 bytes, lines 11923-12216
11923 forEach($rootElement, function(node, i) { 11924 if (node == compileNode) { 11925 $rootElement[i] = $compileNode[0]; 11926 } 11927 }); 11928 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 11929 11930 while (linkQueue.length) { 11931 var scope = linkQueue.shift(), 11932 beforeTemplateLinkNode = linkQueue.shift(), 11933 linkRootElement = linkQueue.shift(), 11934 boundTranscludeFn = linkQueue.shift(), 11935 linkNode = $compileNode[0]; 11936 11937 if (scope.$$destroyed) continue; 11938 11939 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 11940 var oldClasses = beforeTemplateLinkNode.className; 11941 11942 if (!(previousCompileContext.hasElementTranscludeDirective && 11943 origAsyncDirective.replace)) { 11944 // it was cloned therefore we have to clone as well. 11945 linkNode = jqLiteClone(compileNode); 11946 } 11947 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 11948 11949 // Copy in CSS classes from original node 11950 safeAddClass(jqLite(linkNode), oldClasses); 11951 } 11952 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 11953 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 11954 } else { 11955 childBoundTranscludeFn = boundTranscludeFn; 11956 } 11957 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 11958 childBoundTranscludeFn); 11959 } 11960 linkQueue = null; 11961 }); 11962 11963 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 11964 var childBoundTranscludeFn = boundTranscludeFn; 11965 if (scope.$$destroyed) return; 11966 if (linkQueue) { 11967 linkQueue.push(scope, 11968 node, 11969 rootElement, 11970 childBoundTranscludeFn); 11971 } else { 11972 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 11973 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 11974 } 11975 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 11976 } 11977 }; 11978 } 11979 11980 11981 /** 11982 * Sorting function for bound directives. 11983 */ 11984 function byPriority(a, b) { 11985 var diff = b.priority - a.priority; 11986 if (diff !== 0) return diff; 11987 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 11988 return a.index - b.index; 11989 } 11990 11991 function assertNoDuplicate(what, previousDirective, directive, element) { 11992 11993 function wrapModuleNameIfDefined(moduleName) { 11994 return moduleName ? 11995 (' (module: ' + moduleName + ')') : 11996 ''; 11997 } 11998 11999 if (previousDirective) { 12000 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 12001 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 12002 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 12003 } 12004 } 12005 12006 12007 function addTextInterpolateDirective(directives, text) { 12008 var interpolateFn = $interpolate(text, true); 12009 if (interpolateFn) { 12010 directives.push({ 12011 priority: 0, 12012 compile: function textInterpolateCompileFn(templateNode) { 12013 var templateNodeParent = templateNode.parent(), 12014 hasCompileParent = !!templateNodeParent.length; 12015 12016 // When transcluding a template that has bindings in the root 12017 // we don't have a parent and thus need to add the class during linking fn. 12018 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 12019 12020 return function textInterpolateLinkFn(scope, node) { 12021 var parent = node.parent(); 12022 if (!hasCompileParent) compile.$$addBindingClass(parent); 12023 compile.$$addBindingInfo(parent, interpolateFn.expressions); 12024 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 12025 node[0].nodeValue = value; 12026 }); 12027 }; 12028 } 12029 }); 12030 } 12031 } 12032 12033 12034 function wrapTemplate(type, template) { 12035 type = lowercase(type || 'html'); 12036 switch (type) { 12037 case 'svg': 12038 case 'math': 12039 var wrapper = window.document.createElement('div'); 12040 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 12041 return wrapper.childNodes[0].childNodes; 12042 default: 12043 return template; 12044 } 12045 } 12046 12047 12048 function getTrustedContext(node, attrNormalizedName) { 12049 if (attrNormalizedName == "srcdoc") { 12050 return $sce.HTML; 12051 } 12052 var tag = nodeName_(node); 12053 // maction[xlink:href] can source SVG. It's not limited to <maction>. 12054 if (attrNormalizedName == "xlinkHref" || 12055 (tag == "form" && attrNormalizedName == "action") || 12056 (tag != "img" && (attrNormalizedName == "src" || 12057 attrNormalizedName == "ngSrc"))) { 12058 return $sce.RESOURCE_URL; 12059 } 12060 } 12061 12062 12063 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 12064 var trustedContext = getTrustedContext(node, name); 12065 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 12066 12067 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 12068 12069 // no interpolation found -> ignore 12070 if (!interpolateFn) return; 12071 12072 12073 if (name === "multiple" && nodeName_(node) === "select") { 12074 throw $compileMinErr("selmulti", 12075 "Binding to the 'multiple' attribute is not supported. Element: {0}", 12076 startingTag(node)); 12077 } 12078 12079 directives.push({ 12080 priority: 100, 12081 compile: function() { 12082 return { 12083 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 12084 var $$observers = (attr.$$observers || (attr.$$observers = createMap())); 12085 12086 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 12087 throw $compileMinErr('nodomevents', 12088 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 12089 "ng- versions (such as ng-click instead of onclick) instead."); 12090 } 12091 12092 // If the attribute has changed since last $interpolate()ed 12093 var newValue = attr[name]; 12094 if (newValue !== value) { 12095 // we need to interpolate again since the attribute value has been updated 12096 // (e.g. by another directive's compile function) 12097 // ensure unset/empty values make interpolateFn falsy 12098 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 12099 value = newValue; 12100 } 12101 12102 // if attribute was updated so that there is no interpolation going on we don't want to 12103 // register any observers 12104 if (!interpolateFn) return; 12105 12106 // initialize attr object so that it's ready in case we need the value for isolate 12107 // scope initialization, otherwise the value would not be available from isolate 12108 // directive's linking fn during linking phase 12109 attr[name] = interpolateFn(scope); 12110 12111 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 12112 (attr.$$observers && attr.$$observers[name].$$scope || scope). 12113 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 12114 //special case for class attribute addition + removal 12115 //so that class changes can tap into the animation 12116 //hooks provided by the $animate service. Be sure to 12117 //skip animations when the first digest occurs (when 12118 //both the new and the old values are the same) since 12119 //the CSS classes are the non-interpolated values 12120 if (name === 'class' && newValue != oldValue) { 12121 attr.$updateClass(newValue, oldValue); 12122 } else { 12123 attr.$set(name, newValue); 12124 } 12125 }); 12126 } 12127 }; 12128 } 12129 }); 12130 } 12131 12132 12133 /** 12134 * This is a special jqLite.replaceWith, which can replace items which 12135 * have no parents, provided that the containing jqLite collection is provided. 12136 * 12137 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 12138 * in the root of the tree. 12139 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 12140 * the shell, but replace its DOM node reference. 12141 * @param {Node} newNode The new DOM node. 12142 */ 12143 function replaceWith($rootElement, elementsToRemove, newNode) { 12144 var firstElementToRemove = elementsToRemove[0], 12145 removeCount = elementsToRemove.length, 12146 parent = firstElementToRemove.parentNode, 12147 i, ii; 12148 12149 if ($rootElement) { 12150 for (i = 0, ii = $rootElement.length; i < ii; i++) { 12151 if ($rootElement[i] == firstElementToRemove) { 12152 $rootElement[i++] = newNode; 12153 for (var j = i, j2 = j + removeCount - 1, 12154 jj = $rootElement.length; 12155 j < jj; j++, j2++) { 12156 if (j2 < jj) { 12157 $rootElement[j] = $rootElement[j2]; 12158 } else { 12159 delete $rootElement[j]; 12160 } 12161 } 12162 $rootElement.length -= removeCount - 1; 12163 12164 // If the replaced element is also the jQuery .context then replace it 12165 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 12166 // http://api.jquery.com/context/ 12167 if ($rootElement.context === firstElementToRemove) { 12168 $rootElement.context = newNode; 12169 } 12170 break; 12171 } 12172 } 12173 } 12174 12175 if (parent) { 12176 parent.replaceChild(newNode, firstElementToRemove); 12177 } 12178 12179 // Append all the `elementsToRemove` to a fragment. This will... 12180 // - remove them from the DOM 12181 // - allow them to still be traversed with .nextSibling 12182 // - allow a single fragment.qSA to fetch all elements being removed 12183 var fragment = window.document.createDocumentFragment(); 12184 for (i = 0; i < removeCount; i++) { 12185 fragment.appendChild(elementsToRemove[i]); 12186 } 12187 12188 if (jqLite.hasData(firstElementToRemove)) { 12189 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 12190 // data here because there's no public interface in jQuery to do that and copying over 12191 // event listeners (which is the main use of private data) wouldn't work anyway. 12192 jqLite.data(newNode, jqLite.data(firstElementToRemove)); 12193 12194 // Remove $destroy event listeners from `firstElementToRemove` 12195 jqLite(firstElementToRemove).off('$destroy'); 12196 } 12197 12198 // Cleanup any data/listeners on the elements and children. 12199 // This includes invoking the $destroy event on any elements with listeners. 12200 jqLite.cleanData(fragment.querySelectorAll('*')); 12201 12202 // Update the jqLite collection to only contain the `newNode` 12203 for (i = 1; i < removeCount; i++) { 12204 delete elementsToRemove[i]; 12205 } 12206 elementsToRemove[0] = newNode; 12207 elementsToRemove.length = 1; 12208 } 12209 12210 12211 function cloneAndAnnotateFn(fn, annotation) { 12212 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 12213 } 12214 12215 12216 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) {
12217 try { 12218 linkFn(scope, $element, attrs, controllers, transcludeFn); 12219 } catch (e) { 12220 $exceptionHandler(e, startingTag($element)); 12221 } 12222 } 12223 12224 12225 // Set up $watches for isolate scope and controller bindings. This process 12226 // only occurs for isolate scopes and new scopes with controllerAs. 12227 function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) { 12228 var removeWatchCollection = []; 12229 var initialChanges = {}; 12230 var changes; 12231 forEach(bindings, function initializeBinding(definition, scopeName) { 12232 var attrName = definition.attrName, 12233 optional = definition.optional, 12234 mode = definition.mode, // @, =, or & 12235 lastValue, 12236 parentGet, parentSet, compare, removeWatch; 12237 12238 switch (mode) { 12239 12240 case '@': 12241 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 12242 destination[scopeName] = attrs[attrName] = void 0; 12243 } 12244 attrs.$observe(attrName, function(value) { 12245 if (isString(value) || isBoolean(value)) { 12246 var oldValue = destination[scopeName]; 12247 recordChanges(scopeName, value, oldValue); 12248 destination[scopeName] = value; 12249 } 12250 }); 12251 attrs.$$observers[attrName].$$scope = scope; 12252 lastValue = attrs[attrName]; 12253 if (isString(lastValue)) { 12254 // If the attribute has been provided then we trigger an interpolation to ensure 12255 // the value is there for use in the link fn 12256 destination[scopeName] = $interpolate(lastValue)(scope); 12257 } else if (isBoolean(lastValue)) { 12258 // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted 12259 // the value to boolean rather than a string, so we special case this situation 12260 destination[scopeName] = lastValue; 12261 } 12262 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 12263 break; 12264 12265 case '=': 12266 if (!hasOwnProperty.call(attrs, attrName)) { 12267 if (optional) break; 12268 attrs[attrName] = void 0; 12269 } 12270 if (optional && !attrs[attrName]) break; 12271 12272 parentGet = $parse(attrs[attrName]); 12273 if (parentGet.literal) { 12274 compare = equals; 12275 } else { 12276 compare = function simpleCompare(a, b) { return a === b || (a !== a && b !== b); }; 12277 } 12278 parentSet = parentGet.assign || function() { 12279 // reset the change, or we will throw this exception on every $digest 12280 lastValue = destination[scopeName] = parentGet(scope); 12281 throw $compileMinErr('nonassign', 12282 "Expression '{0}' in attribute '{1}' used with directive '{2}' is non-assignable!", 12283 attrs[attrName], attrName, directive.name); 12284 }; 12285 lastValue = destination[scopeName] = parentGet(scope); 12286 var parentValueWatch = function parentValueWatch(parentValue) { 12287 if (!compare(parentValue, destination[scopeName])) { 12288 // we are out of sync and need to copy 12289 if (!compare(parentValue, lastValue)) { 12290 // parent changed and it has precedence 12291 destination[scopeName] = parentValue; 12292 } else { 12293 // if the parent can be assigned then do so 12294 parentSet(scope, parentValue = destination[scopeName]); 12295 } 12296 } 12297 return lastValue = parentValue; 12298 }; 12299 parentValueWatch.$stateful = true; 12300 if (definition.collection) { 12301 removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 12302 } else { 12303 removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 12304 } 12305 removeWatchCollection.push(removeWatch); 12306 break; 12307 12308 case '<': 12309 if (!hasOwnProperty.call(attrs, attrName)) { 12310 if (optional) break; 12311 attrs[attrName] = void 0; 12312 } 12313 if (optional && !attrs[attrName]) break; 12314 12315 parentGet = $parse(attrs[attrName]); 12316 12317 destination[scopeName] = parentGet(scope); 12318 initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]); 12319 12320 removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) { 12321 if (newValue === oldValue) { 12322 // If the new and old values are identical then this is the first time the watch has been triggered 12323 // So instead we use the current value on the destination as the old value 12324 oldValue = destination[scopeName]; 12325 } 12326 recordChanges(scopeName, newValue, oldValue); 12327 destination[scopeName] = newValue; 12328 }, parentGet.literal); 12329 12330 removeWatchCollection.push(removeWatch); 12331 break; 12332 12333 case '&': 12334 // Don't assign Object.prototype method to scope 12335 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 12336 12337 // Don't assign noop to destination if expression is not val
vendor: 11,231 bytes, lines 12337-12685
12337id 12338 if (parentGet === noop && optional) break; 12339 12340 destination[scopeName] = function(locals) { 12341 return parentGet(scope, locals); 12342 }; 12343 break; 12344 } 12345 }); 12346 12347 function recordChanges(key, currentValue, previousValue) { 12348 if (isFunction(destination.$onChanges) && currentValue !== previousValue) { 12349 // If we have not already scheduled the top level onChangesQueue handler then do so now 12350 if (!onChangesQueue) { 12351 scope.$$postDigest(flushOnChangesQueue); 12352 onChangesQueue = []; 12353 } 12354 // If we have not already queued a trigger of onChanges for this controller then do so now 12355 if (!changes) { 12356 changes = {}; 12357 onChangesQueue.push(triggerOnChangesHook); 12358 } 12359 // If the has been a change on this property already then we need to reuse the previous value 12360 if (changes[key]) { 12361 previousValue = changes[key].previousValue; 12362 } 12363 // Store this change 12364 changes[key] = new SimpleChange(previousValue, currentValue); 12365 } 12366 } 12367 12368 function triggerOnChangesHook() { 12369 destination.$onChanges(changes); 12370 // Now clear the changes so that we schedule onChanges when more changes arrive 12371 changes = undefined; 12372 } 12373 12374 return { 12375 initialChanges: initialChanges, 12376 removeWatches: removeWatchCollection.length && function removeWatches() { 12377 for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) { 12378 removeWatchCollection[i](); 12379 } 12380 } 12381 }; 12382 } 12383 }]; 12384} 12385 12386function SimpleChange(previous, current) { 12387 this.previousValue = previous; 12388 this.currentValue = current; 12389} 12390SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; }; 12391 12392 12393var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 12394/** 12395 * Converts all accepted directives format into proper directive name. 12396 * @param name Name to normalize 12397 */ 12398function directiveNormalize(name) { 12399 return camelCase(name.replace(PREFIX_REGEXP, '')); 12400} 12401 12402/** 12403 * @ngdoc type 12404 * @name $compile.directive.Attributes 12405 * 12406 * @description 12407 * A shared object between directive compile / linking functions which contains normalized DOM 12408 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 12409 * needed since all of these are treated as equivalent in Angular: 12410 * 12411 * ``` 12412 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 12413 * ``` 12414 */ 12415 12416/** 12417 * @ngdoc property 12418 * @name $compile.directive.Attributes#$attr 12419 * 12420 * @description 12421 * A map of DOM element attribute names to the normalized name. This is 12422 * needed to do reverse lookup from normalized name back to actual name. 12423 */ 12424 12425 12426/** 12427 * @ngdoc method 12428 * @name $compile.directive.Attributes#$set 12429 * @kind function 12430 * 12431 * @description 12432 * Set DOM element attribute value. 12433 * 12434 * 12435 * @param {string} name Normalized element attribute name of the property to modify. The name is 12436 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 12437 * property to the original name. 12438 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 12439 */ 12440 12441 12442 12443/** 12444 * Closure compiler type information 12445 */ 12446 12447function nodesetLinkingFn( 12448 /* angular.Scope */ scope, 12449 /* NodeList */ nodeList, 12450 /* Element */ rootElement, 12451 /* function(Function) */ boundTranscludeFn 12452) {} 12453 12454function directiveLinkingFn( 12455 /* nodesetLinkingFn */ nodesetLinkingFn, 12456 /* angular.Scope */ scope, 12457 /* Node */ node, 12458 /* Element */ rootElement, 12459 /* function(Function) */ boundTranscludeFn 12460) {} 12461 12462function tokenDifference(str1, str2) { 12463 var values = '', 12464 tokens1 = str1.split(/\s+/), 12465 tokens2 = str2.split(/\s+/); 12466 12467 outer: 12468 for (var i = 0; i < tokens1.length; i++) { 12469 var token = tokens1[i]; 12470 for (var j = 0; j < tokens2.length; j++) { 12471 if (token == tokens2[j]) continue outer; 12472 } 12473 values += (values.length > 0 ? ' ' : '') + token; 12474 } 12475 return values; 12476} 12477 12478function removeComments(jqNodes) { 12479 jqNodes = jqLite(jqNodes); 12480 var i = jqNodes.length; 12481 12482 if (i <= 1) { 12483 return jqNodes; 12484 } 12485 12486 while (i--) { 12487 var node = jqNodes[i]; 12488 if (node.nodeType === NODE_TYPE_COMMENT) { 12489 splice.call(jqNodes, i, 1); 12490 } 12491 } 12492 return jqNodes; 12493} 12494 12495var $controllerMinErr = minErr('$controller'); 12496 12497 12498var CNTRL_REG = /^(\S+)(\s+as\s+([\w$]+))?$/; 12499function identifierForController(controller, ident) { 12500 if (ident && isString(ident)) return ident; 12501 if (isString(controller)) { 12502 var match = CNTRL_REG.exec(controller); 12503 if (match) return match[3]; 12504 } 12505} 12506 12507 12508/** 12509 * @ngdoc provider 12510 * @name $controllerProvider 12511 * @description 12512 * The {@link ng.$controller $controller service} is used by Angular to create new 12513 * controllers. 12514 * 12515 * This provider allows controller registration via the 12516 * {@link ng.$controllerProvider#register register} method. 12517 */ 12518function $ControllerProvider() { 12519 var controllers = {}, 12520 globals = false; 12521 12522 /** 12523 * @ngdoc method 12524 * @name $controllerProvider#has 12525 * @param {string} name Controller name to check. 12526 */ 12527 this.has = function(name) { 12528 return controllers.hasOwnProperty(name); 12529 }; 12530 12531 /** 12532 * @ngdoc method 12533 * @name $controllerProvider#register 12534 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 12535 * the names and the values are the constructors. 12536 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 12537 * annotations in the array notation). 12538 */ 12539 this.register = function(name, constructor) { 12540 assertNotHasOwnProperty(name, 'controller'); 12541 if (isObject(name)) { 12542 extend(controllers, name); 12543 } else { 12544 controllers[name] = constructor; 12545 } 12546 }; 12547 12548 /** 12549 * @ngdoc method 12550 * @name $controllerProvider#allowGlobals 12551 * @description If called, allows `$controller` to find controller constructors on `window` 12552 */ 12553 this.allowGlobals = function() { 12554 globals = true; 12555 }; 12556 12557 12558 this.$get = ['$injector', '$window', function($injector, $window) { 12559 12560 /** 12561 * @ngdoc service 12562 * @name $controller 12563 * @requires $injector 12564 * 12565 * @param {Function|string} constructor If called with a function then it's considered to be the 12566 * controller constructor function. Otherwise it's considered to be a string which is used 12567 * to retrieve the controller constructor using the following steps: 12568 * 12569 * * check if a controller with given name is registered via `$controllerProvider` 12570 * * check if evaluating the string on the current scope returns a constructor 12571 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 12572 * `window` object (not recommended) 12573 * 12574 * The string can use the `controller as property` syntax, where the controller instance is published 12575 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 12576 * to work correctly. 12577 * 12578 * @param {Object} locals Injection locals for Controller. 12579 * @return {Object} Instance of given controller. 12580 * 12581 * @description 12582 * `$controller` service is responsible for instantiating controllers. 12583 * 12584 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 12585 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 12586 */ 12587 return function $controller(expression, locals, later, ident) { 12588 // PRIVATE API: 12589 // param `later` --- indicates that the controller's constructor is invoked at a later time. 12590 // If true, $controller will allocate the object with the correct 12591 // prototype chain, but will not invoke the controller until a returned 12592 // callback is invoked. 12593 // param `ident` --- An optional label which overrides the label parsed from the controller 12594 // expression, if any. 12595 var instance, match, constructor, identifier; 12596 later = later === true; 12597 if (ident && isString(ident)) { 12598 identifier = ident; 12599 } 12600 12601 if (isString(expression)) { 12602 match = expression.match(CNTRL_REG); 12603 if (!match) { 12604 throw $controllerMinErr('ctrlfmt', 12605 "Badly formed controller string '{0}'. " + 12606 "Must match `__name__ as __id__` or `__name__`.", expression); 12607 } 12608 constructor = match[1], 12609 identifier = identifier || match[3]; 12610 expression = controllers.hasOwnProperty(constructor) 12611 ? controllers[constructor] 12612 : getter(locals.$scope, constructor, true) || 12613 (globals ? getter($window, constructor, true) : undefined); 12614 12615 assertArgFn(expression, constructor, true); 12616 } 12617 12618 if (later) { 12619 // Instantiate controller later: 12620 // This machinery is used to create an instance of the object before calling the 12621 // controller's constructor itself. 12622 // 12623 // This allows properties to be added to the controller before the constructor is 12624 // invoked. Primarily, this is used for isolate scope bindings in $compile. 12625 // 12626 // This feature is not intended for use by applications, and is thus not documented 12627 // publicly. 12628 // Object creation: http://jsperf.com/create-constructor/2 12629 var controllerPrototype = (isArray(expression) ? 12630 expression[expression.length - 1] : expression).prototype; 12631 instance = Object.create(controllerPrototype || null); 12632 12633 if (identifier) { 12634 addIdentifier(locals, identifier, instance, constructor || expression.name); 12635 } 12636 12637 var instantiate; 12638 return instantiate = extend(function $controllerInit() { 12639 var result = $injector.invoke(expression, instance, locals, constructor); 12640 if (result !== instance && (isObject(result) || isFunction(result))) { 12641 instance = result; 12642 if (identifier) { 12643 // If result changed, re-assign controllerAs value to scope. 12644 addIdentifier(locals, identifier, instance, constructor || expression.name); 12645 } 12646 } 12647 return instance; 12648 }, { 12649 instance: instance, 12650 identifier: identifier 12651 }); 12652 } 12653 12654 instance = $injector.instantiate(expression, locals, constructor); 12655 12656 if (identifier) { 12657 addIdentifier(locals, identifier, instance, constructor || expression.name); 12658 } 12659 12660 return instance; 12661 }; 12662 12663 function addIdentifier(locals, identifier, instance, name) { 12664 if (!(locals && isObject(locals.$scope))) { 12665 throw minErr('$controller')('noscp', 12666 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 12667 name, identifier); 12668 } 12669 12670 locals.$scope[identifier] = instance; 12671 } 12672 }]; 12673} 12674 12675/** 12676 * @ngdoc service 12677 * @name $document 12678 * @requires $window 12679 * 12680 * @description 12681 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 12682 * 12683 * @example 12684 <example module="documentExample"> 12685 <file name="index.html">
12686 <div ng-controller="ExampleController"> 12687 <p>$document title: <b ng-bind="title"></b></p> 12688 <p>window.document title: <b ng-bind="windowTitle"></b></p> 12689 </div> 12690 </file> 12691 <file name="script.js"> 12692 angular.module('documentExample', []) 12693 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 12694 $scope.title = $document[0].title; 12695 $scope.windowTitle = angular.element(window.document)[0].title; 12696 }]); 12697 </file> 12698 </example> 12699 */ 12700function $DocumentProvider() { 12701 this.$get = ['$window', function(window) { 12702 return jqLite(window.document); 12703 }]; 12704} 12705 12706/** 12707 * @ngdoc service 12708 * @name $exceptionHandler 12709 * @requires ng.$log 12710 * 12711 * @description 12712 * Any uncaught exception in angular expressions is delegated to this service. 12713 * The default implementation simply delegates to `$log.error` which logs it into 12714 * the browser console. 12715 * 12716 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 12717 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 12718 * 12719 * ## Example: 12720 * 12721 * ```js 12722 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 12723 * return function(exception, cause) { 12724 * exception.message += ' (caused by "' + cause + '")'; 12725 * throw exception; 12726 * }; 12727 * }); 12728 * ``` 12729 * 12730 * This example will override the normal action of `$exceptionHandler`, to make angular 12731 * exceptions fail hard when they happen, instead of just logging to the console. 12732 * 12733 * <hr /> 12734 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 12735 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 12736 * (unless executed during a digest). 12737 * 12738 * If you wish, you can manually delegate exceptions, e.g. 12739 * `try { ... } catch(e) { $exceptionHandler(e); }` 12740 * 12741 * @param {Error} exception Exception associated with the error. 12742 * @param {string=} cause optional information about the context in which 12743 * the error was thrown. 12744 * 12745 */ 12746function $ExceptionHandlerProvider() { 12747 this.$get = ['$log', function($log) { 12748 return function(exception, cause) { 12749 $log.error.apply($log, arguments); 12750 }; 12751 }]; 12752} 12753 12754var $$ForceReflowProvider = function() { 12755 this.$get = ['$document', function($document) { 12756 return function(domNode) { 12757 //the line below will force the browser to perform a repaint so 12758 //that all the animated elements within the animation frame will 12759 //be properly updated and drawn on screen. This is required to 12760 //ensure that the preparation animation is properly flushed so that 12761 //the active state picks up from there. DO NOT REMOVE THIS LINE. 12762 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 12763 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 12764 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 12765 if (domNode) { 12766 if (!domNode.nodeType && domNode instanceof jqLite) { 12767 domNode = domNode[0]; 12768 } 12769 } else { 12770 domNode = $document[0].body; 12771 } 12772 return domNode.offsetWidth + 1; 12773 }; 12774 }]; 12775}; 12776 12777var APPLICATION_JSON = 'application/json'; 12778var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 12779var JSON_START = /^\[|^\{(?!\{)/; 12780var JSON_ENDS = { 12781 '[': /]$/, 12782 '{': /}$/ 12783}; 12784var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 12785var $httpMinErr = minErr('$http'); 12786var $httpMinErrLegacyFn = function(method) { 12787 return function() { 12788 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); 12789 }; 12790}; 12791 12792function serializeValue(v) { 12793 if (isObject(v)) { 12794 return isDate(v) ? v.toISOString() : toJson(v); 12795 } 12796 return v; 12797} 12798 12799 12800function $HttpParamSerializerProvider() { 12801 /** 12802 * @ngdoc service 12803 * @name $httpParamSerializer 12804 * @description 12805 * 12806 * Default {@link $http `$http`} params serializer that converts objects to strings 12807 * according to the following rules: 12808 * 12809 * * `{'foo': 'bar'}` results in `foo=bar` 12810 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 12811 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 12812 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 12813 * 12814 * Note that serializer will sort the request parameters alphabetically. 12815 * */ 12816 12817 this.$get = function() { 12818 return function ngParamSerializer(params) { 12819 if (!params) return ''; 12820 var parts = [];
vendor: 3,679 bytes, lines 12821-12947
12821 forEachSorted(params, function(value, key) { 12822 if (value === null || isUndefined(value)) return; 12823 if (isArray(value)) { 12824 forEach(value, function(v) { 12825 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 12826 }); 12827 } else { 12828 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 12829 } 12830 }); 12831 12832 return parts.join('&'); 12833 }; 12834 }; 12835} 12836 12837function $HttpParamSerializerJQLikeProvider() { 12838 /** 12839 * @ngdoc service 12840 * @name $httpParamSerializerJQLike 12841 * @description 12842 * 12843 * Alternative {@link $http `$http`} params serializer that follows 12844 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 12845 * The serializer will also sort the params alphabetically. 12846 * 12847 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 12848 * 12849 * ```js 12850 * $http({ 12851 * url: myUrl, 12852 * method: 'GET', 12853 * params: myParams, 12854 * paramSerializer: '$httpParamSerializerJQLike' 12855 * }); 12856 * ``` 12857 * 12858 * It is also possible to set it as the default `paramSerializer` in the 12859 * {@link $httpProvider#defaults `$httpProvider`}. 12860 * 12861 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 12862 * form data for submission: 12863 * 12864 * ```js 12865 * .controller(function($http, $httpParamSerializerJQLike) { 12866 * //... 12867 * 12868 * $http({ 12869 * url: myUrl, 12870 * method: 'POST', 12871 * data: $httpParamSerializerJQLike(myData), 12872 * headers: { 12873 * 'Content-Type': 'application/x-www-form-urlencoded' 12874 * } 12875 * }); 12876 * 12877 * }); 12878 * ``` 12879 * 12880 * */ 12881 this.$get = function() { 12882 return function jQueryLikeParamSerializer(params) { 12883 if (!params) return ''; 12884 var parts = []; 12885 serialize(params, '', true); 12886 return parts.join('&'); 12887 12888 function serialize(toSerialize, prefix, topLevel) { 12889 if (toSerialize === null || isUndefined(toSerialize)) return; 12890 if (isArray(toSerialize)) { 12891 forEach(toSerialize, function(value, index) { 12892 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 12893 }); 12894 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 12895 forEachSorted(toSerialize, function(value, key) { 12896 serialize(value, prefix + 12897 (topLevel ? '' : '[') + 12898 key + 12899 (topLevel ? '' : ']')); 12900 }); 12901 } else { 12902 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 12903 } 12904 } 12905 }; 12906 }; 12907} 12908 12909function defaultHttpResponseTransform(data, headers) { 12910 if (isString(data)) { 12911 // Strip json vulnerability protection prefix and trim whitespace 12912 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 12913 12914 if (tempData) { 12915 var contentType = headers('Content-Type'); 12916 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 12917 data = fromJson(tempData); 12918 } 12919 } 12920 } 12921 12922 return data; 12923} 12924 12925function isJsonLike(str) { 12926 var jsonStart = str.match(JSON_START); 12927 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 12928} 12929 12930/** 12931 * Parse headers into key value object 12932 * 12933 * @param {string} headers Raw headers as a string 12934 * @returns {Object} Parsed headers as key value object 12935 */ 12936function parseHeaders(headers) { 12937 var parsed = createMap(), i; 12938 12939 function fillInParsed(key, val) { 12940 if (key) { 12941 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 12942 } 12943 } 12944 12945 if (isString(headers)) { 12946 forEach(headers.split('\n'), function(line) { 12947 i = line.indexOf(':');
vendor: 4,132 bytes, lines 12948-13084
12948 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 12949 }); 12950 } else if (isObject(headers)) { 12951 forEach(headers, function(headerVal, headerKey) { 12952 fillInParsed(lowercase(headerKey), trim(headerVal)); 12953 }); 12954 } 12955 12956 return parsed; 12957} 12958 12959 12960/** 12961 * Returns a function that provides access to parsed headers. 12962 * 12963 * Headers are lazy parsed when first requested. 12964 * @see parseHeaders 12965 * 12966 * @param {(string|Object)} headers Headers to provide access to. 12967 * @returns {function(string=)} Returns a getter function which if called with: 12968 * 12969 * - if called with single an argument returns a single header value or null 12970 * - if called with no arguments returns an object containing all headers. 12971 */ 12972function headersGetter(headers) { 12973 var headersObj; 12974 12975 return function(name) { 12976 if (!headersObj) headersObj = parseHeaders(headers); 12977 12978 if (name) { 12979 var value = headersObj[lowercase(name)]; 12980 if (value === void 0) { 12981 value = null; 12982 } 12983 return value; 12984 } 12985 12986 return headersObj; 12987 }; 12988} 12989 12990 12991/** 12992 * Chain all given functions 12993 * 12994 * This function is used for both request and response transforming 12995 * 12996 * @param {*} data Data to transform. 12997 * @param {function(string=)} headers HTTP headers getter fn. 12998 * @param {number} status HTTP status code of the response. 12999 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 13000 * @returns {*} Transformed data. 13001 */ 13002function transformData(data, headers, status, fns) { 13003 if (isFunction(fns)) { 13004 return fns(data, headers, status); 13005 } 13006 13007 forEach(fns, function(fn) { 13008 data = fn(data, headers, status); 13009 }); 13010 13011 return data; 13012} 13013 13014 13015function isSuccess(status) { 13016 return 200 <= status && status < 300; 13017} 13018 13019 13020/** 13021 * @ngdoc provider 13022 * @name $httpProvider 13023 * @description 13024 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 13025 * */ 13026function $HttpProvider() { 13027 /** 13028 * @ngdoc property 13029 * @name $httpProvider#defaults 13030 * @description 13031 * 13032 * Object containing default values for all {@link ng.$http $http} requests. 13033 * 13034 * - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with 13035 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses 13036 * by default. See {@link $http#caching $http Caching} for more information. 13037 * 13038 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 13039 * Defaults value is `'XSRF-TOKEN'`. 13040 * 13041 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 13042 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 13043 * 13044 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 13045 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 13046 * setting default headers. 13047 * - **`defaults.headers.common`** 13048 * - **`defaults.headers.post`** 13049 * - **`defaults.headers.put`** 13050 * - **`defaults.headers.patch`** 13051 * 13052 * 13053 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 13054 * used to the prepare string representation of request parameters (specified as an object). 13055 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 13056 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 13057 * 13058 **/ 13059 var defaults = this.defaults = { 13060 // transform incoming response data 13061 transformResponse: [defaultHttpResponseTransform], 13062 13063 // transform outgoing request data 13064 transformRequest: [function(d) { 13065 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 13066 }], 13067 13068 // default headers 13069 headers: { 13070 common: { 13071 'Accept': 'application/json, text/plain, */*' 13072 }, 13073 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 13074 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 13075 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 13076 }, 13077 13078 xsrfCookieName: 'XSRF-TOKEN', 13079 xsrfHeaderName: 'X-XSRF-TOKEN', 13080 13081 paramSerializer: '$httpParamSerializer' 13082 }; 13083 13084 var useApplyAsync = false;
vendor: 4,307 bytes, lines 13085-13195
13085 /** 13086 * @ngdoc method 13087 * @name $httpProvider#useApplyAsync 13088 * @description 13089 * 13090 * Configure $http service to combine processing of multiple http responses received at around 13091 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 13092 * significant performance improvement for bigger applications that make many HTTP requests 13093 * concurrently (common during application bootstrap). 13094 * 13095 * Defaults to false. If no value is specified, returns the current configured value. 13096 * 13097 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 13098 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 13099 * to load and share the same digest cycle. 13100 * 13101 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 13102 * otherwise, returns the current configured value. 13103 **/ 13104 this.useApplyAsync = function(value) { 13105 if (isDefined(value)) { 13106 useApplyAsync = !!value; 13107 return this; 13108 } 13109 return useApplyAsync; 13110 }; 13111 13112 var useLegacyPromise = true; 13113 /** 13114 * @ngdoc method 13115 * @name $httpProvider#useLegacyPromiseExtensions 13116 * @description 13117 * 13118 * Configure `$http` service to return promises without the shorthand methods `success` and `error`. 13119 * This should be used to make sure that applications work without these methods. 13120 * 13121 * Defaults to true. If no value is specified, returns the current configured value. 13122 * 13123 * @param {boolean=} value If true, `$http` will return a promise with the deprecated legacy `success` and `error` methods. 13124 * 13125 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 13126 * otherwise, returns the current configured value. 13127 **/ 13128 this.useLegacyPromiseExtensions = function(value) { 13129 if (isDefined(value)) { 13130 useLegacyPromise = !!value; 13131 return this; 13132 } 13133 return useLegacyPromise; 13134 }; 13135 13136 /** 13137 * @ngdoc property 13138 * @name $httpProvider#interceptors 13139 * @description 13140 * 13141 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 13142 * pre-processing of request or postprocessing of responses. 13143 * 13144 * These service factories are ordered by request, i.e. they are applied in the same order as the 13145 * array, on request, but reverse order, on response. 13146 * 13147 * {@link ng.$http#interceptors Interceptors detailed info} 13148 **/ 13149 var interceptorFactories = this.interceptors = []; 13150 13151 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 13152 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 13153 13154 var defaultCache = $cacheFactory('$http'); 13155 13156 /** 13157 * Make sure that default param serializer is exposed as a function 13158 */ 13159 defaults.paramSerializer = isString(defaults.paramSerializer) ? 13160 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 13161 13162 /** 13163 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 13164 * The reversal is needed so that we can build up the interception chain around the 13165 * server request. 13166 */ 13167 var reversedInterceptors = []; 13168 13169 forEach(interceptorFactories, function(interceptorFactory) { 13170 reversedInterceptors.unshift(isString(interceptorFactory) 13171 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 13172 }); 13173 13174 /** 13175 * @ngdoc service 13176 * @kind function 13177 * @name $http 13178 * @requires ng.$httpBackend 13179 * @requires $cacheFactory 13180 * @requires $rootScope 13181 * @requires $q 13182 * @requires $injector 13183 * 13184 * @description 13185 * The `$http` service is a core Angular service that facilitates communication with the remote 13186 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 13187 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 13188 * 13189 * For unit testing applications that use `$http` service, see 13190 * {@link ngMock.$httpBackend $httpBackend mock}. 13191 * 13192 * For a higher level of abstraction, please check out the {@link ngResource.$resource 13193 * $resource} service. 13194 * 13195 * The $http API is based on the {@link ng.$q deferred/promise APIs}
13195 exposed by 13196 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 13197 * it is important to familiarize yourself with these APIs and the guarantees they provide. 13198 * 13199 * 13200 * ## General usage 13201 * The `$http` service is a function which takes a single argument â a {@link $http#usage configuration object} â 13202 * that is used to generate an HTTP request and returns a {@link ng.$q promise}. 13203 * 13204 * ```js 13205 * // Simple GET request example: 13206 * $http({ 13207 * method: 'GET', 13208 * url: '/someUrl' 13209 * }).then(function successCallback(response) { 13210 * // this callback will be called asynchronously 13211 * // when the response is available 13212 * }, function errorCallback(response) { 13213 * // called asynchronously if an error occurs 13214 * // or server returns response with an error status. 13215 * }); 13216 * ``` 13217 * 13218 * The response object has these properties: 13219 * 13220 * - **data** â `{string|Object}` â The response body transformed with the transform 13221 * functions. 13222 * - **status** â `{number}` â HTTP status code of the response. 13223 * - **headers** â `{function([headerName])}` â Header getter function. 13224 * - **config** â `{Object}` â The configuration object that was used to generate the request. 13225 * - **statusText** â `{string}` â HTTP status text of the response. 13226 * 13227 * A response status code between 200 and 299 is considered a success status and 13228 * will result in the success callback being called. Note that if the response is a redirect, 13229 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 13230 * called for such responses. 13231 * 13232 * 13233 * ## Shortcut methods 13234 * 13235 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 13236 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 13237 * last argument. 13238 * 13239 * ```js 13240 * $http.get('/someUrl', config).then(successCallback, errorCallback); 13241 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 13242 * ``` 13243 * 13244 * Complete list of shortcut methods: 13245 * 13246 * - {@link ng.$http#get $http.get} 13247 * - {@link ng.$http#head $http.head} 13248 * - {@link ng.$http#post $http.post} 13249 * - {@link ng.$http#put $http.put} 13250 * - {@link ng.$http#delete $http.delete} 13251 * - {@link ng.$http#jsonp $http.jsonp} 13252 * - {@link ng.$http#patch $http.patch} 13253 * 13254 * 13255 * ## Writing Unit Tests that use $http 13256 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 13257 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 13258 * request using trained responses. 13259 * 13260 * ``` 13261 * $httpBackend.expectGET(...); 13262 * $http.get(...); 13263 * $httpBackend.flush(); 13264 * ``` 13265 * 13266 * ## Deprecation Notice 13267 * <div class="alert alert-danger"> 13268 * The `$http` legacy promise methods `success` and `error` have been deprecated. 13269 * Use the standard `then` method instead. 13270 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to 13271 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. 13272 * </div> 13273 * 13274 * ## Setting HTTP Headers 13275 * 13276 * The $http service will automatically add certain HTTP headers to all requests. These defaults 13277 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 13278 * object, which currently contains this default configuration: 13279 * 13280 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 13281 * - `Accept: application/json, text/plain, * / *` 13282 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 13283 * - `Content-Type: application/json` 13284 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 13285 * - `Content-Type: application/json` 13286 *
13287 * To add or overwrite these defaults, simply add or remove a property from these configuration 13288 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 13289 * with the lowercased HTTP method name as the key, e.g. 13290 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 13291 * 13292 * The defaults can also be set at runtime via the `$http.defaults` object in the same 13293 * fashion. For example: 13294 * 13295 * ``` 13296 * module.run(function($http) { 13297 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; 13298 * }); 13299 * ``` 13300 * 13301 * In addition, you can supply a `headers` property in the config object passed when 13302 * calling `$http(config)`, which overrides the defaults without changing them globally. 13303 * 13304 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 13305 * Use the `headers` property, setting the desired header to `undefined`. For example: 13306 * 13307 * ```js 13308 * var req = { 13309 * method: 'POST', 13310 * url: 'http://example.com', 13311 * headers: { 13312 * 'Content-Type': undefined 13313 * }, 13314 * data: { test: 'test' } 13315 * } 13316 * 13317 * $http(req).then(function(){...}, function(){...}); 13318 * ``` 13319 * 13320 * ## Transforming Requests and Responses 13321 * 13322 * Both requests and responses can be transformed using transformation functions: `transformRequest` 13323 * and `transformResponse`. These properties can be a single function that returns 13324 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 13325 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 13326 * 13327 * <div class="alert alert-warning"> 13328 * **Note:** Angular does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline. 13329 * That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference). 13330 * For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest 13331 * function will be reflected on the scope and in any templates where the object is data-bound. 13332 * To prevent this, transform functions should have no side-effects. 13333 * If you need to modify properties, it is recommended to make a copy of the data, or create new object to return. 13334 * </div> 13335 * 13336 * ### Default Transformations 13337 * 13338 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 13339 * `defaults.transformResponse` properties. If a request does not provide its own transformations 13340 * then these will be applied. 13341 * 13342 * You can augment or replace the default transformations by modifying these properties by adding to or 13343 * replacing the array. 13344 * 13345 * Angular provides the following default transformations: 13346 * 13347 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 13348 * 13349 * - If the `data` property of the request configuration object contains an object, serialize it 13350 * into JSON format. 13351 * 13352 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 13353 * 13354 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 13355 * - If JSON response is detected, deserialize it using a JSON parser. 13356 * 13357 * 13358 * ### Overriding the Default Transformations Per Request 13359 * 13360 * If you wish override the request/response transformations only for a single request then provide 13361 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 13362 * into `$http`. 13363 * 13364 * Note that if you provide these properties on the config object the default transformations will be 13365 * overwritten. If you wish to augment the default transformations then you must include them in your 13366 * local transformation array.
13367 * 13368 * The following code demonstrates adding a new response transformation to be run after the default response 13369 * transformations have been run. 13370 * 13371 * ```js 13372 * function appendTransform(defaults, transform) { 13373 * 13374 * // We can't guarantee that the default transformation is an array 13375 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 13376 * 13377 * // Append the new transformation to the defaults 13378 * return defaults.concat(transform); 13379 * } 13380 * 13381 * $http({ 13382 * url: '...', 13383 * method: 'GET', 13384 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 13385 * return doTransform(value); 13386 * }) 13387 * }); 13388 * ``` 13389 * 13390 * 13391 * ## Caching 13392 * 13393 * {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must 13394 * set the config.cache value or the default cache value to TRUE or to a cache object (created 13395 * with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes 13396 * precedence over the default cache value. 13397 * 13398 * In order to: 13399 * * cache all responses - set the default cache value to TRUE or to a cache object 13400 * * cache a specific response - set config.cache value to TRUE or to a cache object 13401 * 13402 * If caching is enabled, but neither the default cache nor config.cache are set to a cache object, 13403 * then the default `$cacheFactory($http)` object is used. 13404 * 13405 * The default cache value can be set by updating the 13406 * {@link ng.$http#defaults `$http.defaults.cache`} property or the 13407 * {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property. 13408 * 13409 * When caching is enabled, {@link ng.$http `$http`} stores the response from the server using 13410 * the relevant cache object. The next time the same request is made, the response is returned 13411 * from the cache without sending a request to the server. 13412 * 13413 * Take note that: 13414 * 13415 * * Only GET and JSONP requests are cached. 13416 * * The cache key is the request URL including search parameters; headers are not considered. 13417 * * Cached responses are returned asynchronously, in the same way as responses from the server. 13418 * * If multiple identical requests are made using the same cache, which is not yet populated, 13419 * one request will be made to the server and remaining requests will return the same response. 13420 * * A cache-control header on the response does not affect if or how responses are cached. 13421 * 13422 * 13423 * ## Interceptors 13424 * 13425 * Before you start creating interceptors, be sure to understand the 13426 * {@link ng.$q $q and deferred/promise APIs}. 13427 * 13428 * For purposes of global error handling, authentication, or any kind of synchronous or 13429 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 13430 * able to intercept requests before they are handed to the server and 13431 * responses before they are handed over to the application code that 13432 * initiated these requests. The interceptors leverage the {@link ng.$q 13433 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 13434 * 13435 * The interceptors are service factories that are registered with the `$httpProvider` by 13436 * adding them to the `$httpProvider.interceptors` array. The factory is called and 13437 * injected with dependencies (if specified) and returns the interceptor. 13438 * 13439 * There are two kinds of interceptors (and two kinds of rejection interceptors): 13440 * 13441 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 13442 * modify the `config` object or create a new one. The function needs to return the `config` 13443 * object directly, or a promise containing the `config` or a new `config` object. 13444 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 13445 * resolved with a rejection. 13446 * * `response`: interceptors get called with http `response` object. The function is free to 13447 * modify the `response` object or create a new one. The function needs to return the `response` 13448 * object directly, or as a promise containing the `response` or a new `response` object. 13449 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 13450 * resolved with a rejection. 13451 * 13452 * 13453 * ```js 13454 * // register the interceptor as a service 13455 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 13456 * return { 13457 * // optional method 13458 * 'request': function(config) {
13459 * // do something on success 13460 * return config; 13461 * }, 13462 * 13463 * // optional method 13464 * 'requestError': function(rejection) { 13465 * // do something on error 13466 * if (canRecover(rejection)) { 13467 * return responseOrNewPromise 13468 * } 13469 * return $q.reject(rejection); 13470 * }, 13471 * 13472 * 13473 * 13474 * // optional method 13475 * 'response': function(response) { 13476 * // do something on success 13477 * return response; 13478 * }, 13479 * 13480 * // optional method 13481 * 'responseError': function(rejection) { 13482 * // do something on error 13483 * if (canRecover(rejection)) { 13484 * return responseOrNewPromise 13485 * } 13486 * return $q.reject(rejection); 13487 * } 13488 * }; 13489 * }); 13490 * 13491 * $httpProvider.interceptors.push('myHttpInterceptor'); 13492 * 13493 * 13494 * // alternatively, register the interceptor via an anonymous factory 13495 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 13496 * return { 13497 * 'request': function(config) { 13498 * // same as above 13499 * }, 13500 * 13501 * 'response': function(response) { 13502 * // same as above 13503 * } 13504 * }; 13505 * }); 13506 * ``` 13507 * 13508 * ## Security Considerations 13509 * 13510 * When designing web applications, consider security threats from: 13511 * 13512 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 13513 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 13514 * 13515 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 13516 * pre-configured with strategies that address these issues, but for this to work backend server 13517 * cooperation is required. 13518 * 13519 * ### JSON Vulnerability Protection 13520 * 13521 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 13522 * allows third party website to turn your JSON resource URL into 13523 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 13524 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 13525 * Angular will automatically strip the prefix before processing it as JSON. 13526 * 13527 * For example if your server needs to return: 13528 * ```js 13529 * ['one','two'] 13530 * ``` 13531 * 13532 * which is vulnerable to attack, your server can return: 13533 * ```js 13534 * )]}', 13535 * ['one','two'] 13536 * ``` 13537 * 13538 * Angular will strip the prefix, before processing the JSON. 13539 * 13540 * 13541 * ### Cross Site Request Forgery (XSRF) Protection 13542 * 13543 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by 13544 * which the attacker can trick an authenticated user into unknowingly executing actions on your 13545 * website. Angular provides a mechanism to counter XSRF. When performing XHR requests, the 13546 * $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP 13547 * header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the 13548 * cookie, your server can be assured that the XHR came from JavaScript running on your domain. 13549 * The header will not be set for cross-domain requests. 13550 * 13551 * To take advantage of this, your server needs to set a token in a JavaScript readable session 13552 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 13553 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 13554 * that only JavaScript running on your domain could have sent the request. The token must be 13555 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 13556 * making up its own tokens). We recommend that the token is a digest of your site's 13557 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 13558 * for added security. 13559 * 13560 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 13561 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 13562 * or the per-request config object. 13563 * 13564 * In order to prevent collisions in environments where multiple Angular apps share the 13565 * same domain or subdomain, we recommend that each application uses unique cookie name. 13566 * 13567 * @param {object} config Object describing the request to be made and how it should be 13568 * processed. The object has following properties: 13569 * 13570 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 13571 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 13572 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 13573 * with the `paramSerializer` and appended as GET parameters. 13574 * - **data** â `{string|Object}` â Data to be sent as the request message data. 13575 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 13576 * HTTP headers to send to the server. If the return value of a function is null, the 13577 * header will not be sent. Functions accept a config object as an argument. 13578 * - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object. 13579 * To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`. 13580 * The handler will be called in the context of a `$apply` block. 13581 * - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload 13582 * object. To bind events to the XMLHttpRequest object, use `eventHandlers`. 13583 * The handler will be called in the context of a `$apply` block. 13584 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 13585 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 13586 * - **transformRequest** â 13587 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
13587}` â 13588 * transform function or an array of such functions. The transform function takes the http 13589 * request body and headers and returns its transformed (typically serialized) version. 13590 * See {@link ng.$http#overriding-the-default-transformations-per-request 13591 * Overriding the Default Transformations} 13592 * - **transformResponse** â 13593 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 13594 * transform function or an array of such functions. The transform function takes the http 13595 * response body, headers and status and returns its transformed (typically deserialized) version. 13596 * See {@link ng.$http#overriding-the-default-transformations-per-request 13597 * Overriding the Default Transformations} 13598 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 13599 * prepare the string representation of request parameters (specified as an object). 13600 * If specified as string, it is interpreted as function registered with the 13601 * {@link $injector $injector}, which means you can create your own serializer 13602 * by registering it as a {@link auto.$provide#service service}. 13603 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 13604 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 13605 * - **cache** â `{boolean|Object}` â A boolean value or object created with 13606 * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response. 13607 * See {@link $http#caching $http Caching} for more information. 13608 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 13609 * that should abort the request when resolved. 13610 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 13611 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 13612 * for more information. 13613 * - **responseType** - `{string}` - see 13614 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 13615 * 13616 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object 13617 * when the request succeeds or fails. 13618 * 13619 * 13620 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 13621 * requests. This is primarily meant to be used for debugging purposes. 13622 * 13623 * 13624 * @example 13625<example module="httpExample"> 13626<file name="index.html"> 13627 <div ng-controller="FetchController"> 13628 <select ng-model="method" aria-label="Request method"> 13629 <option>GET</option> 13630 <option>JSONP</option> 13631 </select> 13632 <input type="text" ng-model="url" size="80" aria-label="URL" /> 13633 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 13634 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 13635 <button id="samplejsonpbtn" 13636 ng-click="updateModel('JSONP', 13637 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 13638 Sample JSONP 13639 </button> 13640 <button id="invalidjsonpbtn" 13641 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 13642 Invalid JSONP 13643 </button> 13644 <pre>http status code: {{status}}</pre> 13645 <pre>http response data: {{data}}</pre> 13646 </div> 13647</file> 13648<file name="script.js"> 13649 angular.module('httpExample', []) 13650 .controller('FetchController', ['$scope', '$http', '$templateCache', 13651 function($scope, $http, $templateCache) { 13652 $scope.method = 'GET'; 13653 $scope.url = 'http-hello.html'; 13654 13655 $scope.fetch = function() { 13656 $scope.code = null; 13657 $scope.response = null; 13658 13659 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 13660 then(function(response) { 13661 $scope.status = response.status; 13662 $scope.data = response.data; 13663 }, function(response) { 13664 $scope.data = response.data || "Request failed"; 13665 $scope.status = response.status; 13666 }); 13667 }; 13668 13669 $scope.updateModel = function(method, url) { 13670 $scope.method = method; 13671 $scope.url = url; 13672 }; 13673 }]); 13674</file> 13675<file name="http-hello.html"> 13676 Hello, $http! 13677</file> 13678<file name="protractor.js" type="protractor"> 13679 var status = element(by.binding('status')); 13680 var data = element(by.binding('data')); 13681 var fetchBtn = element(by.id('fetchbtn')); 13682 var sampleGetBtn = element(by.id('samplegetbtn')); 13683 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 13684 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 13685 13686 it('should make an xhr GET request', function() { 13687 sampleGetBtn.click(); 13688 fetchBtn.click(); 13689 expect(status.getText()).toMatch('200'); 13690 expect(data.getText()).toMatch(/Hello, \$http!/); 13691 }); 13692 13693// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 13694// it('should make a JSONP request to angularjs.org', function() { 13695// sampleJsonpBtn.click(); 13696// fetchBtn.click(); 13697// expect(status.getText()).toMatch('200'); 13698// expect(data.getText()).toMatch(/Super Hero!/); 13699// }); 13700
13701 it('should make JSONP request to invalid URL and invoke the error handler', 13702 function() { 13703 invalidJsonpBtn.click(); 13704 fetchBtn.click(); 13705 expect(status.getText()).toMatch('0'); 13706 expect(data.getText()).toMatch('Request failed'); 13707 }); 13708</file> 13709</example> 13710 */ 13711 function $http(requestConfig) { 13712 13713 if (!isObject(requestConfig)) { 13714 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 13715 } 13716 13717 if (!isString(requestConfig.url)) { 13718 throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); 13719 } 13720 13721 var config = extend({ 13722 method: 'get', 13723 transformRequest: defaults.transformRequest, 13724 transformResponse: defaults.transformResponse, 13725 paramSerializer: defaults.paramSerializer 13726 }, requestConfig); 13727 13728 config.headers = mergeHeaders(requestConfig); 13729 config.method = uppercase(config.method); 13730 config.paramSerializer = isString(config.paramSerializer) ? 13731 $injector.get(config.paramSerializer) : config.paramSerializer; 13732 13733 var serverRequest = function(config) { 13734 var headers = config.headers; 13735 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 13736 13737 // strip content-type if data is undefined 13738 if (isUndefined(reqData)) { 13739 forEach(headers, function(value, header) { 13740 if (lowercase(header) === 'content-type') { 13741 delete headers[header]; 13742 } 13743 }); 13744 } 13745 13746 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 13747 config.withCredentials = defaults.withCredentials; 13748 } 13749 13750 // send request 13751 return sendReq(config, reqData).then(transformResponse, transformResponse); 13752 }; 13753 13754 var chain = [serverRequest, undefined]; 13755 var promise = $q.when(config); 13756 13757 // apply interceptors 13758 forEach(reversedInterceptors, function(interceptor) { 13759 if (interceptor.request || interceptor.requestError) { 13760 chain.unshift(interceptor.request, interceptor.requestError); 13761 } 13762 if (interceptor.response || interceptor.responseError) { 13763 chain.push(interceptor.response, interceptor.responseError); 13764 } 13765 }); 13766 13767 while (chain.length) { 13768 var thenFn = chain.shift(); 13769 var rejectFn = chain.shift(); 13770 13771 promise = promise.then(thenFn, rejectFn); 13772 } 13773 13774 if (useLegacyPromise) { 13775 promise.success = function(fn) { 13776 assertArgFn(fn, 'fn'); 13777 13778 promise.then(function(response) { 13779 fn(response.data, response.status, response.headers, config); 13780 }); 13781 return promise; 13782 }; 13783 13784 promise.error = function(fn) { 13785 assertArgFn(fn, 'fn'); 13786 13787 promise.then(null, function(response) { 13788 fn(response.data, response.status, response.headers, config); 13789 }); 13790 return promise; 13791 }; 13792 } else { 13793 promise.success = $httpMinErrLegacyFn('success'); 13794 promise.error = $httpMinErrLegacyFn('error'); 13795 } 13796 13797 return promise; 13798 13799 function transformResponse(response) { 13800 // make a copy since the response must be cacheable 13801 var resp = extend({}, response); 13802 resp.data = transformData(response.data, response.headers, response.status, 13803 config.transformResponse); 13804 return (isSuccess(response.status)) 13805 ? resp 13806 : $q.reject(resp); 13807 } 13808 13809 function executeHeaderFns(headers, config) { 13810 var headerContent, processedHeaders = {}; 13811 13812 forEach(headers, function(headerFn, header) { 13813 if (isFunction(headerFn)) { 13814 headerContent = headerFn(config); 13815 if (headerContent != null) { 13816 processedHeaders[header] = headerContent; 13817 } 13818 } else { 13819 processedHeaders[header] = headerFn; 13820 } 13821 }); 13822 13823 return processedHeaders; 13824 } 13825 13826 function mergeHeaders(config) { 13827 var defHeaders = defaults.headers, 13828 reqHeaders = extend({}, config.headers), 13829 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 13830 13831 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 13832
vendor: 9,890 bytes, lines 13833-14157
13833 // using for-in instead of forEach to avoid unnecessary iteration after header has been found 13834 defaultHeadersIteration: 13835 for (defHeaderName in defHeaders) { 13836 lowercaseDefHeaderName = lowercase(defHeaderName); 13837 13838 for (reqHeaderName in reqHeaders) { 13839 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 13840 continue defaultHeadersIteration; 13841 } 13842 } 13843 13844 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 13845 } 13846 13847 // execute if header value is a function for merged headers 13848 return executeHeaderFns(reqHeaders, shallowCopy(config)); 13849 } 13850 } 13851 13852 $http.pendingRequests = []; 13853 13854 /** 13855 * @ngdoc method 13856 * @name $http#get 13857 * 13858 * @description 13859 * Shortcut method to perform `GET` request. 13860 * 13861 * @param {string} url Relative or absolute URL specifying the destination of the request 13862 * @param {Object=} config Optional configuration object 13863 * @returns {HttpPromise} Future object 13864 */ 13865 13866 /** 13867 * @ngdoc method 13868 * @name $http#delete 13869 * 13870 * @description 13871 * Shortcut method to perform `DELETE` request. 13872 * 13873 * @param {string} url Relative or absolute URL specifying the destination of the request 13874 * @param {Object=} config Optional configuration object 13875 * @returns {HttpPromise} Future object 13876 */ 13877 13878 /** 13879 * @ngdoc method 13880 * @name $http#head 13881 * 13882 * @description 13883 * Shortcut method to perform `HEAD` request. 13884 * 13885 * @param {string} url Relative or absolute URL specifying the destination of the request 13886 * @param {Object=} config Optional configuration object 13887 * @returns {HttpPromise} Future object 13888 */ 13889 13890 /** 13891 * @ngdoc method 13892 * @name $http#jsonp 13893 * 13894 * @description 13895 * Shortcut method to perform `JSONP` request. 13896 * 13897 * @param {string} url Relative or absolute URL specifying the destination of the request. 13898 * The name of the callback should be the string `JSON_CALLBACK`. 13899 * @param {Object=} config Optional configuration object 13900 * @returns {HttpPromise} Future object 13901 */ 13902 createShortMethods('get', 'delete', 'head', 'jsonp'); 13903 13904 /** 13905 * @ngdoc method 13906 * @name $http#post 13907 * 13908 * @description 13909 * Shortcut method to perform `POST` request. 13910 * 13911 * @param {string} url Relative or absolute URL specifying the destination of the request 13912 * @param {*} data Request content 13913 * @param {Object=} config Optional configuration object 13914 * @returns {HttpPromise} Future object 13915 */ 13916 13917 /** 13918 * @ngdoc method 13919 * @name $http#put 13920 * 13921 * @description 13922 * Shortcut method to perform `PUT` request. 13923 * 13924 * @param {string} url Relative or absolute URL specifying the destination of the request 13925 * @param {*} data Request content 13926 * @param {Object=} config Optional configuration object 13927 * @returns {HttpPromise} Future object 13928 */ 13929 13930 /** 13931 * @ngdoc method 13932 * @name $http#patch 13933 * 13934 * @description 13935 * Shortcut method to perform `PATCH` request. 13936 * 13937 * @param {string} url Relative or absolute URL specifying the destination of the request 13938 * @param {*} data Request content 13939 * @param {Object=} config Optional configuration object 13940 * @returns {HttpPromise} Future object 13941 */ 13942 createShortMethodsWithData('post', 'put', 'patch'); 13943 13944 /** 13945 * @ngdoc property 13946 * @name $http#defaults 13947 * 13948 * @description 13949 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 13950 * default headers, withCredentials as well as request and response transformations. 13951 * 13952 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 13953 */ 13954 $http.defaults = defaults; 13955 13956 13957 return $http; 13958 13959 13960 function createShortMethods(names) { 13961 forEach(arguments, function(name) { 13962 $http[name] = function(url, config) { 13963 return $http(extend({}, config || {}, { 13964 method: name, 13965 url: url 13966 })); 13967 }; 13968 }); 13969 } 13970 13971 13972 function createShortMethodsWithData(name) { 13973 forEach(arguments, function(name) { 13974 $http[name] = function(url, data, config) { 13975 return $http(extend({}, config || {}, { 13976 method: name, 13977 url: url, 13978 data: data 13979 })); 13980 }; 13981 }); 13982 } 13983 13984 13985 /** 13986 * Makes the request. 13987 * 13988 * !!! ACCESSES CLOSURE VARS: 13989 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 13990 */ 13991 function sendReq(config, reqData) { 13992 var deferred = $q.defer(), 13993 promise = deferred.promise, 13994 cache, 13995 cachedResp, 13996 reqHeaders = config.headers, 13997 url = buildUrl(config.url, config.paramSerializer(config.params)); 13998 13999 $http.pendingRequests.push(config); 14000 promise.then(removePendingReq, removePendingReq); 14001 14002 14003 if ((config.cache || defaults.cache) && config.cache !== false && 14004 (config.method === 'GET' || config.method === 'JSONP')) { 14005 cache = isObject(config.cache) ? config.cache 14006 : isObject(defaults.cache) ? defaults.cache 14007 : defaultCache; 14008 } 14009 14010 if (cache) { 14011 cachedResp = cache.get(url); 14012 if (isDefined(cachedResp)) { 14013 if (isPromiseLike(cachedResp)) { 14014 // cached request has already been sent, but there is no response yet 14015 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 14016 } else { 14017 // serving from cache 14018 if (isArray(cachedResp)) { 14019 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 14020 } else { 14021 resolvePromise(cachedResp, 200, {}, 'OK'); 14022 } 14023 } 14024 } else { 14025 // put the promise for the non-transformed response into cache as a placeholder 14026 cache.put(url, promise); 14027 } 14028 } 14029 14030 14031 // if we won't have the response in cache, set the xsrf headers and 14032 // send the request to the backend 14033 if (isUndefined(cachedResp)) { 14034 var xsrfValue = urlIsSameOrigin(config.url) 14035 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 14036 : undefined; 14037 if (xsrfValue) { 14038 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 14039 } 14040 14041 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 14042 config.withCredentials, config.responseType, 14043 createApplyHandlers(config.eventHandlers), 14044 createApplyHandlers(config.uploadEventHandlers)); 14045 } 14046 14047 return promise; 14048 14049 function createApplyHandlers(eventHandlers) { 14050 if (eventHandlers) { 14051 var applyHandlers = {}; 14052 forEach(eventHandlers, function(eventHandler, key) { 14053 applyHandlers[key] = function(event) { 14054 if (useApplyAsync) { 14055 $rootScope.$applyAsync(callEventHandler); 14056 } else if ($rootScope.$$phase) { 14057 callEventHandler(); 14058 } else { 14059 $rootScope.$apply(callEventHandler); 14060 } 14061 14062 function callEventHandler() { 14063 eventHandler(event); 14064 } 14065 }; 14066 }); 14067 return applyHandlers; 14068 } 14069 } 14070 14071 14072 /** 14073 * Callback registered to $httpBackend(): 14074 * - caches the response if desired 14075 * - resolves the raw $http promise 14076 * - calls $apply 14077 */ 14078 function done(status, response, headersString, statusText) { 14079 if (cache) { 14080 if (isSuccess(status)) { 14081 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 14082 } else { 14083 // remove promise from the cache 14084 cache.remove(url); 14085 } 14086 } 14087 14088 function resolveHttpPromise() { 14089 resolvePromise(response, status, headersString, statusText); 14090 } 14091 14092 if (useApplyAsync) { 14093 $rootScope.$applyAsync(resolveHttpPromise); 14094 } else { 14095 resolveHttpPromise(); 14096 if (!$rootScope.$$phase) $rootScope.$apply(); 14097 } 14098 } 14099 14100 14101 /** 14102 * Resolves the raw $http promise. 14103 */ 14104 function resolvePromise(response, status, headers, statusText) { 14105 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 14106 status = status >= -1 ? status : 0; 14107 14108 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 14109 data: response, 14110 status: status, 14111 headers: headersGetter(headers), 14112 config: config, 14113 statusText: statusText 14114 }); 14115 } 14116 14117 function resolvePromiseWithResult(result) { 14118 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 14119 } 14120 14121 function removePendingReq() { 14122 var idx = $http.pendingRequests.indexOf(config); 14123 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 14124 } 14125 } 14126 14127 14128 function buildUrl(url, serializedParams) { 14129 if (serializedParams.length > 0) { 14130 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 14131 } 14132 return url; 14133 } 14134 }]; 14135} 14136 14137/** 14138 * @ngdoc service 14139 * @name $xhrFactory 14140 * 14141 * @description 14142 * Factory function used to create XMLHttpRequest objects. 14143 * 14144 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 14145 * 14146 * ``` 14147 * angular.module('myApp', []) 14148 * .factory('$xhrFactory', function() { 14149 * return function createXhr(method, url) { 14150 * return new window.XMLHttpRequest({mozSystem: true}); 14151 * }; 14152 * }); 14153 * ``` 14154 * 14155 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 14156 * @param {string} url URL of the request. 14157 */
14158function $xhrFactoryProvider() { 14159 this.$get = function() { 14160 return function createXhr() { 14161 return new window.XMLHttpRequest(); 14162 }; 14163 }; 14164} 14165 14166/** 14167 * @ngdoc service 14168 * @name $httpBackend 14169 * @requires $window 14170 * @requires $document 14171 * @requires $xhrFactory 14172 * 14173 * @description 14174 * HTTP backend used by the {@link ng.$http service} that delegates to 14175 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 14176 * 14177 * You should never need to use this service directly, instead use the higher-level abstractions: 14178 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 14179 * 14180 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 14181 * $httpBackend} which can be trained with responses. 14182 */ 14183function $HttpBackendProvider() { 14184 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) { 14185 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]); 14186 }]; 14187} 14188 14189function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 14190 // TODO(vojta): fix the signature 14191 return function(method, url, post, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { 14192 $browser.$$incOutstandingRequestCount(); 14193 url = url || $browser.url(); 14194 14195 if (lowercase(method) == 'jsonp') { 14196 var callbackId = '_' + (callbacks.counter++).toString(36); 14197 callbacks[callbackId] = function(data) { 14198 callbacks[callbackId].data = data; 14199 callbacks[callbackId].called = true; 14200 }; 14201 14202 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 14203 callbackId, function(status, text) { 14204 completeRequest(callback, status, callbacks[callbackId].data, "", text); 14205 callbacks[callbackId] = noop; 14206 }); 14207 } else { 14208 14209 var xhr = createXhr(method, url); 14210 14211 xhr.open(method, url, true); 14212 forEach(headers, function(value, key) { 14213 if (isDefined(value)) { 14214 xhr.setRequestHeader(key, value); 14215 } 14216 }); 14217 14218 xhr.onload = function requestLoaded() { 14219 var statusText = xhr.statusText || ''; 14220 14221 // responseText is the old-school way of retrieving response (supported by IE9) 14222 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 14223 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 14224 14225 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 14226 var status = xhr.status === 1223 ? 204 : xhr.status; 14227 14228 // fix status code when it is 0 (0 status is undocumented). 14229 // Occurs when accessing file resources or on Android 4.1 stock browser 14230 // while retrieving files from application cache. 14231 if (status === 0) { 14232 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 14233 } 14234 14235 completeRequest(callback, 14236 status, 14237 response, 14238 xhr.getAllResponseHeaders(), 14239 statusText); 14240 }; 14241 14242 var requestError = function() { 14243 // The response is always empty 14244 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 14245 completeRequest(callback, -1, null, null, ''); 14246 }; 14247 14248 xhr.onerror = requestError; 14249 xhr.onabort = requestError; 14250 14251 forEach(eventHandlers, function(value, key) { 14252 xhr.addEventListener(key, value); 14253 }); 14254 14255 forEach(uploadEventHandlers, function(value, key) { 14256 xhr.upload.addEventListener(key, value); 14257 }); 14258 14259 if (withCredentials) { 14260 xhr.withCredentials = true; 14261 } 14262 14263 if (responseType) { 14264 try { 14265 xhr.responseType = responseType; 14266 } catch (e) { 14267 // WebKit added support for the json responseType value on 09/03/2013 14268 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 14269 // known to throw when setting the value "json" as the response type. Other older 14270 // browsers implementing the responseType 14271 // 14272 // The json response type can be ignored if not supported, because JSON payloads are 14273 // parsed on the client-side regardless. 14274 if (responseType !== 'json') { 14275 throw e; 14276 } 14277 } 14278 } 14279
14280 xhr.send(isUndefined(post) ? null : post); 14281 } 14282 14283 if (timeout > 0) { 14284 var timeoutId = $browserDefer(timeoutRequest, timeout); 14285 } else if (isPromiseLike(timeout)) { 14286 timeout.then(timeoutRequest); 14287 } 14288 14289 14290 function timeoutRequest() { 14291 jsonpDone && jsonpDone(); 14292 xhr && xhr.abort(); 14293 } 14294 14295 function completeRequest(callback, status, response, headersString, statusText) { 14296 // cancel timeout and subsequent timeout promise resolution 14297 if (isDefined(timeoutId)) { 14298 $browserDefer.cancel(timeoutId); 14299 } 14300 jsonpDone = xhr = null; 14301 14302 callback(status, response, headersString, statusText); 14303 $browser.$$completeOutstandingRequest(noop); 14304 } 14305 }; 14306 14307 function jsonpReq(url, callbackId, done) { 14308 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 14309 // - fetches local scripts via XHR and evals them 14310 // - adds and immediately removes script elements from the document 14311 var script = rawDocument.createElement('script'), callback = null; 14312 script.type = "text/javascript"; 14313 script.src = url; 14314 script.async = true; 14315 14316 callback = function(event) { 14317 removeEventListenerFn(script, "load", callback); 14318 removeEventListenerFn(script, "error", callback); 14319 rawDocument.body.removeChild(script); 14320 script = null; 14321 var status = -1; 14322 var text = "unknown"; 14323 14324 if (event) { 14325 if (event.type === "load" && !callbacks[callbackId].called) { 14326 event = { type: "error" }; 14327 } 14328 text = event.type; 14329 status = event.type === "error" ? 404 : 200; 14330 } 14331 14332 if (done) { 14333 done(status, text); 14334 } 14335 }; 14336 14337 addEventListenerFn(script, "load", callback); 14338 addEventListenerFn(script, "error", callback); 14339 rawDocument.body.appendChild(script); 14340 return callback; 14341 } 14342} 14343 14344var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 14345$interpolateMinErr.throwNoconcat = function(text) { 14346 throw $interpolateMinErr('noconcat', 14347 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 14348 "interpolations that concatenate multiple expressions when a trusted value is " + 14349 "required. See http://docs.angularjs.org/api/ng.$sce", text); 14350}; 14351 14352$interpolateMinErr.interr = function(text, err) { 14353 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 14354}; 14355 14356/** 14357 * @ngdoc provider 14358 * @name $interpolateProvider 14359 * 14360 * @description 14361 *
14362 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 14363 * 14364 * <div class="alert alert-danger"> 14365 * This feature is sometimes used to mix different markup languages, e.g. to wrap an Angular 14366 * template within a Python Jinja template (or any other template language). Mixing templating 14367 * languages is **very dangerous**. The embedding template language will not safely escape Angular 14368 * expressions, so any user-controlled values in the template will cause Cross Site Scripting (XSS) 14369 * security bugs! 14370 * </div> 14371 * 14372 * @example 14373<example name="custom-interpolation-markup" module="customInterpolationApp"> 14374<file name="index.html"> 14375<script> 14376 var customInterpolationApp = angular.module('customInterpolationApp', []); 14377 14378 customInterpolationApp.config(function($interpolateProvider) { 14379 $interpolateProvider.startSymbol('//'); 14380 $interpolateProvider.endSymbol('//'); 14381 }); 14382 14383 14384 customInterpolationApp.controller('DemoController', function() { 14385 this.label = "This binding is brought you by // interpolation symbols."; 14386 }); 14387</script> 14388<div ng-controller="DemoController as demo"> 14389 //demo.label// 14390</div> 14391</file> 14392<file name="protractor.js" type="protractor"> 14393 it('should interpolate binding with custom symbols', function() { 14394 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 14395 }); 14396</file> 14397</example> 14398 */ 14399function $InterpolateProvider() { 14400 var startSymbol = '{{'; 14401 var endSymbol = '}}'; 14402 14403 /** 14404 * @ngdoc method 14405 * @name $interpolateProvider#startSymbol 14406 * @description 14407 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 14408 * 14409 * @param {string=} value new value to set the starting symbol to. 14410 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 14411 */ 14412 this.startSymbol = function(value) { 14413 if (value) { 14414 startSymbol = value; 14415 return this; 14416 } else { 14417 return startSymbol; 14418 } 14419 }; 14420 14421 /** 14422 * @ngdoc method 14423 * @name $interpolateProvider#endSymbol 14424 * @description 14425 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 14426 * 14427 * @param {string=} value new value to set the ending symbol to. 14428 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 14429 */ 14430 this.endSymbol = function(value) { 14431 if (value) { 14432 endSymbol = value; 14433 return this; 14434 } else { 14435 return endSymbol; 14436 } 14437 }; 14438 14439 14440 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 14441 var startSymbolLength = startSymbol.length, 14442 endSymbolLength = endSymbol.length, 14443 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 14444 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 14445 14446 function escape(ch) { 14447 return '\\\\\\' + ch; 14448 } 14449 14450 function unescapeText(text) { 14451 return text.replace(escapedStartRegexp, startSymbol). 14452 replace(escapedEndRegexp, endSymbol); 14453 } 14454 14455 function stringify(value) { 14456 if (value == null) { // null || undefined 14457 return ''; 14458 } 14459 switch (typeof value) { 14460 case 'string': 14461 break; 14462 case 'number': 14463 value = '' + value; 14464 break; 14465 default: 14466 value = toJson(value); 14467 } 14468 14469 return value; 14470 } 14471 14472 //TODO: this is the same as the constantWatchDelegate in parse.js 14473 function constantWatchDelegate(scope, listener, objectEquality, constantInterp) { 14474 var unwatch; 14475 return unwatch = scope.$watch(function constantInterpolateWatch(scope) { 14476 unwatch(); 14477 return constantInterp(scope); 14478 }, listener, objectEquality); 14479 } 14480 14481 /** 14482 * @ngdoc service 14483 * @name $interpolate 14484 * @kind function 14485 * 14486 * @requires $parse 14487 * @requires $sce 14488 * 14489 * @description 14490 * 14491 * Compiles a string with markup into an interpolation function. This service is used by the 14492 * HTML {@link ng.$compile $compile} service for data binding. See 14493 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 14494 * interpolation markup. 14495 * 14496 * 14497 * ```js 14498 * var $interpolate = ...; // injected 14499 * var exp = $interpolate('Hello {{name | uppercase}}!'); 14500 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); 14501 * ``` 14502 * 14503 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 14504 * `true`, the interpolation function will return `undefined` unless all embedded expressions 14505 * evaluate to a value other than `undefined`. 14506 * 14507 * ```js 14508 * var $interpolate = ...; // injected 14509 * var context = {greeting: 'Hello', name: undefined }; 14510 * 14511 * // default "forgiving" mode 14512 * var exp = $interpolate('{{greeting}} {{name}}!'); 14513 * expect(exp(context)).toEqual('Hello !'); 14514 * 14515 * // "allOrNothing" mode 14516 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 14517 * expect(exp(context)).toBeUndefined(); 14518 * context.name = 'Angular'; 14519 * expect(exp(context)).toEqual('Hello Angular!'); 14520 * ``` 14521 * 14522 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 14523 * 14524 * ####Escaped Interpolation 14525 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 14526 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 14527 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 14528 * or binding. 14529 * 14530 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 14531 * degree, while also enabling code examples to work without relying on the 14532 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 14533 * 14534 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 14535 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 14536 * interpolation start/end markers with their escaped counterparts.** 14537 * 14538 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 14539 * output when the $interpolate service processes the text. So, for HTML elements interpolated 14540 * by {@link ng.$compile $compile}
14540, or otherwise interpolated with the `mustHaveExpression` parameter 14541 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 14542 * this is typically useful only when user-data is used in rendering a template from the server, or 14543 * when otherwise untrusted data is used by a directive. 14544 * 14545 * <example> 14546 * <file name="index.html"> 14547 * <div ng-init="username='A user'"> 14548 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 14549 * </p> 14550 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 14551 * application, but fails to accomplish their task, because the server has correctly 14552 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 14553 * characters.</p> 14554 * <p>Instead, the result of the attempted script injection is visible, and can be removed 14555 * from the database by an administrator.</p> 14556 * </div> 14557 * </file> 14558 * </example> 14559 * 14560 * @param {string} text The text with markup to interpolate. 14561 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 14562 * embedded expression in order to return an interpolation function. Strings with no 14563 * embedded expression will return null for the interpolation function. 14564 * @param {string=} trustedContext when provided, the returned function passes the interpolated 14565 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 14566 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 14567 * provides Strict Contextual Escaping for details. 14568 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 14569 * unless all embedded expressions evaluate to a value other than `undefined`. 14570 * @returns {function(context)} an interpolation function which is used to compute the 14571 * interpolated string. The function has these parameters: 14572 * 14573 * - `context`: evaluation context for all expressions embedded in the interpolated text 14574 */ 14575 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 14576 // Provide a quick exit and simplified result function for text with no interpolation 14577 if (!text.length || text.indexOf(startSymbol) === -1) { 14578 var constantInterp; 14579 if (!mustHaveExpression) { 14580 var unescapedText = unescapeText(text); 14581 constantInterp = valueFn(unescapedText); 14582 constantInterp.exp = text; 14583 constantInterp.expressions = []; 14584 constantInterp.$$watchDelegate = constantWatchDelegate; 14585 } 14586 return constantInterp; 14587 } 14588 14589 allOrNothing = !!allOrNothing; 14590 var startIndex, 14591 endIndex, 14592 index = 0, 14593 expressions = [], 14594 parseFns = [], 14595 textLength = text.length, 14596 exp, 14597 concat = [], 14598 expressionPositions = []; 14599 14600 while (index < textLength) { 14601 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 14602 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 14603 if (index !== startIndex) { 14604 concat.push(unescapeText(text.substring(index, startIndex))); 14605 } 14606 exp = text.substring(startIndex + startSymbolLength, endIndex); 14607 expressions.push(exp); 14608 parseFns.push($parse(exp, parseStringifyInterceptor)); 14609 index = endIndex + endSymbolLength; 14610 expressionPositions.push(concat.length); 14611 concat.push(''); 14612 } else { 14613 // we did not find an interpolation, so we have to add the remainder to the separators array 14614 if (index !== textLength) { 14615 concat.push(unescapeText(text.substring(index))); 14616 } 14617 break; 14618 } 14619 } 14620 14621 // Concatenating expressions makes it hard to reason about whether some combination of 14622 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 14623 // single expression be used for iframe[src], object[src], etc., we ensure that the value 14624 // that's used is assigned or constructed by some JS code somewhere that is more testable or 14625 // make it obvious that you bound the value to some user controlled value. This helps reduce 14626 // the load when auditing for XSS issues. 14627 if (trustedContext && concat.length > 1) { 14628 $interpolateMinErr.throwNoconcat(text); 14629 } 14630 14631 if (!mustHaveExpression || expressions.length) { 14632 var compute = function(values) { 14633 for (var i = 0, ii = expressions.length; i < ii; i++) { 14634 if (allOrNothing && isUndefined(values[i])) return; 14635 concat[expressionPositions[i]] = values[i]; 14636 } 14637 return concat.join(''); 14638 }; 14639 14640 var getValue = function(value) { 14641 return trustedContext ? 14642 $sce.getTrusted(trustedContext, value) : 14643 $sce.valueOf(value); 14644 }; 14645 14646 return extend(function interpolationFn(context) { 14647 var i = 0; 14648 var ii = expressions.length; 14649 var values = new Array(ii); 14650 14651 try { 14652 for (; i < ii; i++) { 14653 values[i] = parseFns[i](context); 14654 } 14655 14656 return compute(values); 14657 } catch (err) { 14658 $exceptionHandler($interpolateMinErr.interr(text, err)); 14659 } 14660 14661 }, { 14662 // all of these properties are undocumented for now 14663 exp: text, //just for compatibility with regular watchers created via $watch 14664 expressions: expressions, 14665 $$watchDelegate: function(scope, listener) { 14666 var lastValue; 14667 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 14668 var currValue = compute(values); 14669 if (isFunction(listener)) { 14670 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 14671 }
14672 lastValue = currValue; 14673 }); 14674 } 14675 }); 14676 } 14677 14678 function parseStringifyInterceptor(value) { 14679 try { 14680 value = getValue(value); 14681 return allOrNothing && !isDefined(value) ? value : stringify(value); 14682 } catch (err) { 14683 $exceptionHandler($interpolateMinErr.interr(text, err)); 14684 } 14685 } 14686 } 14687 14688 14689 /** 14690 * @ngdoc method 14691 * @name $interpolate#startSymbol 14692 * @description 14693 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 14694 * 14695 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 14696 * the symbol. 14697 * 14698 * @returns {string} start symbol. 14699 */ 14700 $interpolate.startSymbol = function() { 14701 return startSymbol; 14702 }; 14703 14704 14705 /** 14706 * @ngdoc method 14707 * @name $interpolate#endSymbol 14708 * @description 14709 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 14710 * 14711 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 14712 * the symbol. 14713 * 14714 * @returns {string} end symbol. 14715 */ 14716 $interpolate.endSymbol = function() { 14717 return endSymbol; 14718 }; 14719 14720 return $interpolate; 14721 }]; 14722} 14723 14724function $IntervalProvider() { 14725 this.$get = ['$rootScope', '$window', '$q', '$$q', '$browser', 14726 function($rootScope, $window, $q, $$q, $browser) { 14727 var intervals = {}; 14728 14729 14730 /** 14731 * @ngdoc service 14732 * @name $interval 14733 * 14734 * @description 14735 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 14736 * milliseconds. 14737 * 14738 * The return value of registering an interval function is a promise. This promise will be 14739 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 14740 * run indefinitely if `count` is not defined. The value of the notification will be the 14741 * number of iterations that have run. 14742 * To cancel an interval, call `$interval.cancel(promise)`. 14743 * 14744 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 14745 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 14746 * time. 14747 * 14748 * <div class="alert alert-warning"> 14749 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 14750 * with them. In particular they are not automatically destroyed when a controller's scope or a 14751 * directive's element are destroyed. 14752 * You should take this into consideration and make sure to always cancel the interval at the 14753 * appropriate moment. See the example below for more details on how and when to do this. 14754 * </div> 14755 * 14756 * @param {function()} fn A function that should be called repeatedly. 14757 * @param {number} delay Number of milliseconds between each function call. 14758 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 14759 * indefinitely. 14760 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 14761 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 14762 * @param {...*=} Pass additional parameters to the executed function. 14763 * @returns {promise} A promise which will be notified on each iteration. 14764 * 14765 * @example 14766 * <example module="intervalExample"> 14767 * <file name="index.html"> 14768 * <script> 14769 * angular.module('intervalExample', []) 14770 * .controller('ExampleController', ['$scope', '$interval', 14771 * function($scope, $interval) { 14772 * $scope.format = 'M/d/yy h:mm:ss a'; 14773 * $scope.blood_1 = 100; 14774 * $scope.blood_2 = 120; 14775 * 14776 * var stop; 14777 * $scope.fight = function() { 14778 * // Don't start a new fight if we are already fighting 14779 * if ( angular.isDefined(stop) ) return; 14780 * 14781 * stop = $interval(function() { 14782 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 14783 * $scope.blood_1 = $scope.blood_1 - 3; 14784 * $scope.blood_2 = $scope.blood_2 - 4; 14785 * } else { 14786 * $scope.stopFight(); 14787 * } 14788 * }, 100); 14789 * }; 14790 * 14791 * $scope.stopFight = function() { 14792 * if (angular.isDefined(stop)) { 14793 * $interval.cancel(stop); 14794 * stop = undefined; 14795 * } 14796 * }; 14797 * 14798 * $scope.resetFight = function() { 14799 * $scope.blood_1 = 100; 14800 * $scope.blood_2 = 120; 14801 * }; 14802 * 14803 * $scope.$on('$destroy', function() { 14804 * // Make sure that the interval is destroyed too 14805 * $scope.stopFight(); 14806 * }); 14807 * }]) 14808 * // Register the 'myCurrentTime' directive factory method. 14809 * // We inject $interval and dateFilter service since the factory method is DI. 14810 * .directive('myCurrentTime', ['$interval', 'dateFilter', 14811 * function($interval, dateFilter) { 14812 * // return the directive link function. (compile function not needed) 14813 * return function(scope, element, attrs) { 14814 * var format, // date format 14815 * stopTime; // so that we can cancel the time updates 14816 * 14817 * // used to update the UI 14818 * function updateTime() { 14819 * element.text(dateFilter(new Date(), format)); 14820 * } 14821 * 14822 * // watch the expression, and update the UI on change. 14823 * scope.$watch(attrs.myCurrentTime, function(value) { 14824 * format = value; 14825 * updateTime(); 14826 * }); 14827 * 14828 * stopTime = $interval(updateTime, 1000); 14829 * 14830 * // listen on DOM destroy (removal) event, and cancel the next UI update 14831 * // to prevent updating time after the DOM element was removed. 14832 * element.on('$destroy', function() { 14833 * $interval.cancel(stopTime); 14834 * }); 14835 * } 14836 * }]); 14837 * </script> 14838 * 14839 * <div>
14840 * <div ng-controller="ExampleController"> 14841 * <label>Date format: <input ng-model="format"></label> <hr/> 14842 * Current time is: <span my-current-time="format"></span> 14843 * <hr/> 14844 * Blood 1 : <font color='red'>{{blood_1}}</font> 14845 * Blood 2 : <font color='red'>{{blood_2}}</font> 14846 * <button type="button" data-ng-click="fight()">Fight</button> 14847 * <button type="button" data-ng-click="stopFight()">StopFight</button> 14848 * <button type="button" data-ng-click="resetFight()">resetFight</button> 14849 * </div> 14850 * </div> 14851 * 14852 * </file> 14853 * </example> 14854 */ 14855 function interval(fn, delay, count, invokeApply) { 14856 var hasParams = arguments.length > 4, 14857 args = hasParams ? sliceArgs(arguments, 4) : [], 14858 setInterval = $window.setInterval, 14859 clearInterval = $window.clearInterval, 14860 iteration = 0, 14861 skipApply = (isDefined(invokeApply) && !invokeApply), 14862 deferred = (skipApply ? $$q : $q).defer(), 14863 promise = deferred.promise; 14864 14865 count = isDefined(count) ? count : 0; 14866 14867 promise.$$intervalId = setInterval(function tick() { 14868 if (skipApply) { 14869 $browser.defer(callback); 14870 } else { 14871 $rootScope.$evalAsync(callback); 14872 } 14873 deferred.notify(iteration++); 14874 14875 if (count > 0 && iteration >= count) { 14876 deferred.resolve(iteration); 14877 clearInterval(promise.$$intervalId); 14878 delete intervals[promise.$$intervalId]; 14879 } 14880 14881 if (!skipApply) $rootScope.$apply(); 14882 14883 }, delay); 14884 14885 intervals[promise.$$intervalId] = deferred; 14886 14887 return promise; 14888 14889 function callback() { 14890 if (!hasParams) { 14891 fn(iteration); 14892 } else { 14893 fn.apply(null, args); 14894 } 14895 } 14896 } 14897 14898 14899 /** 14900 * @ngdoc method 14901 * @name $interval#cancel 14902 * 14903 * @description 14904 * Cancels a task associated with the `promise`. 14905 * 14906 * @param {Promise=} promise returned by the `$interval` function. 14907 * @returns {boolean} Returns `true` if the task was successfully canceled. 14908 */ 14909 interval.cancel = function(promise) { 14910 if (promise && promise.$$intervalId in intervals) { 14911 intervals[promise.$$intervalId].reject('canceled'); 14912 $window.clearInterval(promise.$$intervalId); 14913 delete intervals[promise.$$intervalId]; 14914 return true; 14915 } 14916 return false; 14917 }; 14918 14919 return interval; 14920 }]; 14921} 14922 14923/** 14924 * @ngdoc service 14925 * @name $locale 14926 * 14927 * @description 14928 * $locale service provides localization rules for various Angular components. As of right now the 14929 * only public api is: 14930 * 14931 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 14932 */ 14933 14934var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 14935 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 14936var $locationMinErr = minErr('$location'); 14937 14938 14939/** 14940 * Encode path using encodeUriSegment, ignoring forward slashes 14941 * 14942 * @param {string} path Path to encode 14943 * @returns {string} 14944 */ 14945function encodePath(path) { 14946 var segments = path.split('/'), 14947 i = segments.length; 14948 14949 while (i--) { 14950 segments[i] = encodeUriSegment(segments[i]); 14951 } 14952 14953 return segments.join('/'); 14954} 14955 14956function parseAbsoluteUrl(absoluteUrl, locationObj) { 14957 var parsedUrl = urlResolve(absoluteUrl); 14958 14959 locationObj.$$protocol = parsedUrl.protocol; 14960 locationObj.$$host = parsedUrl.hostname; 14961 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 14962} 14963 14964 14965function parseAppUrl(relativeUrl, locationObj) { 14966 var prefixed = (relativeUrl.charAt(0) !== '/'); 14967 if (prefixed) { 14968 relativeUrl = '/' + relativeUrl; 14969 } 14970 var match = urlResolve(relativeUrl); 14971 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 14972 match.pathname.substring(1) : match.pathname); 14973 locationObj.$$search = parseKeyValue(match.search); 14974 locationObj.$$hash = decodeURIComponent(match.hash); 14975 14976 // make sure path starts with '/'; 14977 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 14978 locationObj.$$path = '/' + locationObj.$$path; 14979 } 14980} 14981 14982 14983/** 14984 * 14985 * @param {string} begin 14986 * @param {string} whole 14987 * @returns {string} returns text from whole after begin or undefined if it does not begin with 14988 * expected string. 14989 */ 14990function beginsWith(begin, whole) { 14991 if (whole.indexOf(begin) === 0) { 14992 return whole.substr(begin.length); 14993 } 14994} 14995 14996 14997function stripHash(url) { 14998 var index = url.indexOf('#'); 14999 return index == -1 ? url : url.substr(0, index); 15000} 15001 15002function trimEmptyHash(url) { 15003 return url.replace(/(#.+)|#$/, '$1'); 15004} 15005 15006 15007function stripFile(url) { 15008 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 15009} 15010 15011/* return the server only (scheme://host:port) */ 15012function serverBase(url) { 15013 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 15014} 15015 15016 15017/** 15018 * LocationHtml5Url represents an url 15019 * This object is exposed as $location service when HTML5 mode is enabled and supported 15020 * 15021 * @constructor 15022 * @param {string} appBase application base URL 15023 * @param {string} appBaseNoFile application base URL stripped of any filename 15024 * @param {string} basePrefix url path prefix 15025 */ 15026function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 15027 this.$$html5 = true; 15028 basePrefix = basePrefix || ''; 15029 parseAbsoluteUrl(appBase, this); 15030 15031 15032 /** 15033 * Parse given html5 (regular) url string into properties 15034 * @param {string} url HTML5 url 15035 * @private 15036 */ 15037 this.$$parse = function(url) { 15038 var pathUrl = beginsWith(appBaseNoFile, url); 15039 if (!isString(pathUrl)) { 15040 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 15041 appBaseNoFile); 15042 } 15043 15044 parseAppUrl(pathUrl, this); 15045 15046 if (!this.$$path) { 15047 this.$$path = '/'; 15048 } 15049 15050 this.$$compose(); 15051 }; 15052 15053 /** 15054 * Compose url and update `absUrl` property 15055 * @private 15056 */ 15057 this.$$compose = function() {
15058 var search = toKeyValue(this.$$search), 15059 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 15060 15061 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 15062 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 15063 }; 15064 15065 this.$$parseLinkUrl = function(url, relHref) { 15066 if (relHref && relHref[0] === '#') { 15067 // special case for links to hash fragments: 15068 // keep the old url and only replace the hash fragment 15069 this.hash(relHref.slice(1)); 15070 return true; 15071 } 15072 var appUrl, prevAppUrl; 15073 var rewrittenUrl; 15074 15075 if (isDefined(appUrl = beginsWith(appBase, url))) { 15076 prevAppUrl = appUrl; 15077 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) { 15078 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 15079 } else { 15080 rewrittenUrl = appBase + prevAppUrl; 15081 } 15082 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) { 15083 rewrittenUrl = appBaseNoFile + appUrl; 15084 } else if (appBaseNoFile == url + '/') { 15085 rewrittenUrl = appBaseNoFile; 15086 } 15087 if (rewrittenUrl) { 15088 this.$$parse(rewrittenUrl); 15089 } 15090 return !!rewrittenUrl; 15091 }; 15092} 15093 15094 15095/** 15096 * LocationHashbangUrl represents url 15097 * This object is exposed as $location service when developer doesn't opt into html5 mode. 15098 * It also serves as the base class for html5 mode fallback on legacy browsers. 15099 * 15100 * @constructor 15101 * @param {string} appBase application base URL 15102 * @param {string} appBaseNoFile application base URL stripped of any filename 15103 * @param {string} hashPrefix hashbang prefix 15104 */ 15105function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 15106 15107 parseAbsoluteUrl(appBase, this); 15108 15109 15110 /** 15111 * Parse given hashbang url into properties 15112 * @param {string} url Hashbang url 15113 * @private 15114 */ 15115 this.$$parse = function(url) { 15116 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 15117 var withoutHashUrl; 15118 15119 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 15120 15121 // The rest of the url starts with a hash so we have 15122 // got either a hashbang path or a plain hash fragment 15123 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 15124 if (isUndefined(withoutHashUrl)) { 15125 // There was no hashbang prefix so we just have a hash fragment 15126 withoutHashUrl = withoutBaseUrl; 15127 } 15128 15129 } else { 15130 // There was no hashbang path nor hash fragment: 15131 // If we are in HTML5 mode we use what is left as the path; 15132 // Otherwise we ignore what is left 15133 if (this.$$html5) { 15134 withoutHashUrl = withoutBaseUrl; 15135 } else { 15136 withoutHashUrl = ''; 15137 if (isUndefined(withoutBaseUrl)) { 15138 appBase = url; 15139 this.replace(); 15140 } 15141 } 15142 } 15143 15144 parseAppUrl(withoutHashUrl, this); 15145 15146 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 15147 15148 this.$$compose(); 15149 15150 /* 15151 * In Windows, on an anchor node on documents loaded from 15152 * the filesystem, the browser will return a pathname 15153 * prefixed with the drive name ('/C:/path') when a 15154 * pathname without a drive is set: 15155 * * a.setAttribute('href', '/foo') 15156 * * a.pathname === '/C:/foo' //true 15157 * 15158 * Inside of Angular, we're always using pathnames that 15159 * do not include drive names for routing. 15160 */ 15161 function removeWindowsDriveName(path, url, base) { 15162 /* 15163 Matches paths for file protocol on windows, 15164 such as /C:/foo/bar, and captures only /foo/bar. 15165 */ 15166 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 15167 15168 var firstPathSegmentMatch; 15169 15170 //Get the relative path from the input URL. 15171 if (url.indexOf(base) === 0) { 15172 url = url.replace(base, ''); 15173 } 15174 15175 // The input URL intentionally contains a first path segment that ends with a colon. 15176 if (windowsFilePathExp.exec(url)) { 15177 return path; 15178 } 15179 15180 firstPathSegmentMatch = windowsFilePathExp.exec(path); 15181 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 15182 } 15183 }; 15184 15185 /** 15186 * Compose hashbang url and update `absUrl` property 15187 * @private 15188 */ 15189 this.$$compose = function() {
15190 var search = toKeyValue(this.$$search), 15191 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 15192 15193 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 15194 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 15195 }; 15196 15197 this.$$parseLinkUrl = function(url, relHref) { 15198 if (stripHash(appBase) == stripHash(url)) { 15199 this.$$parse(url); 15200 return true; 15201 } 15202 return false; 15203 }; 15204} 15205 15206 15207/** 15208 * LocationHashbangUrl represents url 15209 * This object is exposed as $location service when html5 history api is enabled but the browser 15210 * does not support it. 15211 * 15212 * @constructor 15213 * @param {string} appBase application base URL 15214 * @param {string} appBaseNoFile application base URL stripped of any filename 15215 * @param {string} hashPrefix hashbang prefix 15216 */ 15217function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 15218 this.$$html5 = true; 15219 LocationHashbangUrl.apply(this, arguments); 15220 15221 this.$$parseLinkUrl = function(url, relHref) { 15222 if (relHref && relHref[0] === '#') { 15223 // special case for links to hash fragments: 15224 // keep the old url and only replace the hash fragment 15225 this.hash(relHref.slice(1)); 15226 return true; 15227 } 15228 15229 var rewrittenUrl; 15230 var appUrl; 15231 15232 if (appBase == stripHash(url)) { 15233 rewrittenUrl = url; 15234 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 15235 rewrittenUrl = appBase + hashPrefix + appUrl; 15236 } else if (appBaseNoFile === url + '/') { 15237 rewrittenUrl = appBaseNoFile; 15238 } 15239 if (rewrittenUrl) { 15240 this.$$parse(rewrittenUrl); 15241 } 15242 return !!rewrittenUrl; 15243 }; 15244 15245 this.$$compose = function() { 15246 var search = toKeyValue(this.$$search), 15247 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 15248 15249 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 15250 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 15251 this.$$absUrl = appBase + hashPrefix + this.$$url; 15252 }; 15253 15254} 15255 15256 15257var locationPrototype = { 15258 15259 /** 15260 * Are we in html5 mode? 15261 * @private 15262 */ 15263 $$html5: false, 15264 15265 /** 15266 * Has any change been replacing? 15267 * @private 15268 */ 15269 $$replace: false, 15270 15271 /** 15272 * @ngdoc method 15273 * @name $location#absUrl 15274 * 15275 * @description 15276 * This method is getter only. 15277 * 15278 * Return full url representation with all segments encoded according to rules specified in 15279 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 15280 * 15281 * 15282 * ```js 15283 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 15284 * var absUrl = $location.absUrl(); 15285 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 15286 * ``` 15287 * 15288 * @return {string} full url 15289 */ 15290 absUrl: locationGetter('$$absUrl'), 15291 15292 /** 15293 * @ngdoc method 15294 * @name $location#url 15295 * 15296 * @description 15297 * This method is getter / setter. 15298 * 15299 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 15300 * 15301 * Change path, search and hash, when called with parameter and return `$location`. 15302 * 15303 * 15304 * ```js 15305 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 15306 * var url = $location.url(); 15307 * // => "/some/path?foo=bar&baz=xoxo" 15308 * ``` 15309 * 15310 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 15311 * @return {string} url 15312 */ 15313 url: function(url) { 15314 if (isUndefined(url)) { 15315 return this.$$url; 15316 } 15317 15318 var match = PATH_MATCH.exec(url); 15319 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 15320 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 15321 this.hash(match[5] || ''); 15322 15323 return this; 15324 }, 15325 15326 /** 15327 * @ngdoc method 15328 * @name $location#protocol 15329 * 15330 * @description 15331 * This method is getter only. 15332 * 15333 * Return protocol of current url. 15334 * 15335 * 15336 * ```js 15337 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 15338 * var protocol = $location.protocol(); 15339 * // => "http" 15340 * ``` 15341 * 15342 * @return {string} protocol of current url 15343 */ 15344 protocol: locationGetter('$$protocol'), 15345 15346 /** 15347 * @ngdoc method 15348 * @name $location#host 15349 * 15350 * @description 15351 * This method is getter only. 15352 * 15353 * Return host of current url. 15354 * 15355 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 15356 * 15357 * 15358 * ```js
15359 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 15360 * var host = $location.host(); 15361 * // => "example.com" 15362 * 15363 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 15364 * host = $location.host(); 15365 * // => "example.com" 15366 * host = location.host; 15367 * // => "example.com:8080" 15368 * ``` 15369 * 15370 * @return {string} host of current url. 15371 */ 15372 host: locationGetter('$$host'), 15373 15374 /** 15375 * @ngdoc method 15376 * @name $location#port 15377 * 15378 * @description 15379 * This method is getter only. 15380 * 15381 * Return port of current url. 15382 * 15383 * 15384 * ```js 15385 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 15386 * var port = $location.port(); 15387 * // => 80 15388 * ``` 15389 * 15390 * @return {Number} port 15391 */ 15392 port: locationGetter('$$port'), 15393 15394 /** 15395 * @ngdoc method 15396 * @name $location#path 15397 * 15398 * @description 15399 * This method is getter / setter. 15400 * 15401 * Return path of current url when called without any parameter. 15402 * 15403 * Change path when called with parameter and return `$location`. 15404 * 15405 * Note: Path should always begin with forward slash (/), this method will add the forward slash 15406 * if it is missing. 15407 * 15408 * 15409 * ```js 15410 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 15411 * var path = $location.path(); 15412 * // => "/some/path" 15413 * ``` 15414 * 15415 * @param {(string|number)=} path New path 15416 * @return {string} path 15417 */ 15418 path: locationGetterSetter('$$path', function(path) { 15419 path = path !== null ? path.toString() : ''; 15420 return path.charAt(0) == '/' ? path : '/' + path; 15421 }), 15422 15423 /** 15424 * @ngdoc method 15425 * @name $location#search 15426 * 15427 * @description 15428 * This method is getter / setter. 15429 * 15430 * Return search part (as object) of current url when called without any parameter. 15431 * 15432 * Change search part when called with parameter and return `$location`. 15433 * 15434 * 15435 * ```js 15436 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 15437 * var searchObject = $location.search(); 15438 * // => {foo: 'bar', baz: 'xoxo'} 15439 * 15440 * // set foo to 'yipee' 15441 * $location.search('foo', 'yipee'); 15442 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 15443 * ``` 15444 * 15445 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 15446 * hash object. 15447 * 15448 * When called with a single argument the method acts as a setter, setting the `search` component 15449 * of `$location` to the specified value. 15450 * 15451 * If the argument is a hash object containing an array of values, these values will be encoded 15452 * as duplicate search parameters in the url. 15453 * 15454 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 15455 * will override only a single search property. 15456 * 15457 * If `paramValue` is an array, it will override the property of the `search` component of 15458 * `$location` specified via the first argument. 15459 * 15460 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 15461 * 15462 * If `paramValue` is `true`, the property specified via the first argument will be added with no 15463 * value nor trailing equal sign. 15464 * 15465 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 15466 * one or more arguments returns `$location` object itself. 15467 */ 15468 search: function(search, paramValue) { 15469 switch (arguments.length) { 15470 case 0: 15471 return this.$$search; 15472 case 1: 15473 if (isString(search) || isNumber(search)) { 15474 search = search.toString(); 15475 this.$$search = parseKeyValue(search); 15476 } else if (isObject(search)) { 15477 search = copy(search, {}); 15478 // remove object undefined or null properties 15479 forEach(search, function(value, key) { 15480 if (value == null) delete search[key]; 15481 }); 15482 15483 this.$$search = search; 15484 } else { 15485 throw $locationMinErr('isrcharg', 15486 'The first argument of the `$location#search()` call must be a string or an object.'); 15487 } 15488 break; 15489 default: 15490 if (isUndefined(paramValue) || paramValue === null) { 15491 delete this.$$search[search]; 15492 } else { 15493 this.$$search[search] = paramValue; 15494 } 15495 } 15496 15497 this.$$compose(); 15498 return this; 15499 }, 15500 15501 /** 15502 * @ngdoc method 15503 * @name $location#hash 15504 * 15505 * @description 15506 * This method is getter / setter. 15507 * 15508 * Returns the hash fragment when called without any parameters. 15509 * 15510 * Changes the hash fragment when called with a parameter and returns `$location`. 15511 * 15512 * 15513 * ```js
15514 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 15515 * var hash = $location.hash(); 15516 * // => "hashValue" 15517 * ``` 15518 * 15519 * @param {(string|number)=} hash New hash fragment 15520 * @return {string} hash 15521 */ 15522 hash: locationGetterSetter('$$hash', function(hash) { 15523 return hash !== null ? hash.toString() : ''; 15524 }), 15525 15526 /** 15527 * @ngdoc method 15528 * @name $location#replace 15529 * 15530 * @description 15531 * If called, all changes to $location during the current `$digest` will replace the current history 15532 * record, instead of adding a new one. 15533 */ 15534 replace: function() { 15535 this.$$replace = true; 15536 return this; 15537 } 15538}; 15539 15540forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 15541 Location.prototype = Object.create(locationPrototype); 15542 15543 /** 15544 * @ngdoc method 15545 * @name $location#state 15546 * 15547 * @description 15548 * This method is getter / setter. 15549 * 15550 * Return the history state object when called without any parameter. 15551 * 15552 * Change the history state object when called with one parameter and return `$location`. 15553 * The state object is later passed to `pushState` or `replaceState`. 15554 * 15555 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 15556 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 15557 * older browsers (like IE9 or Android < 4.0), don't use this method. 15558 * 15559 * @param {object=} state State object for pushState or replaceState 15560 * @return {object} state 15561 */ 15562 Location.prototype.state = function(state) { 15563 if (!arguments.length) { 15564 return this.$$state; 15565 } 15566 15567 if (Location !== LocationHtml5Url || !this.$$html5) { 15568 throw $locationMinErr('nostate', 'History API state support is available only ' + 15569 'in HTML5 mode and only in browsers supporting HTML5 History API'); 15570 } 15571 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 15572 // but we're changing the $$state reference to $browser.state() during the $digest 15573 // so the modification window is narrow. 15574 this.$$state = isUndefined(state) ? null : state; 15575 15576 return this; 15577 }; 15578}); 15579 15580 15581function locationGetter(property) { 15582 return function() { 15583 return this[property]; 15584 }; 15585} 15586 15587 15588function locationGetterSetter(property, preprocess) { 15589 return function(value) { 15590 if (isUndefined(value)) { 15591 return this[property]; 15592 } 15593 15594 this[property] = preprocess(value); 15595 this.$$compose(); 15596 15597 return this; 15598 }; 15599} 15600 15601 15602/** 15603 * @ngdoc service 15604 * @name $location 15605 * 15606 * @requires $rootElement 15607 * 15608 * @description 15609 * The $location service parses the URL in the browser address bar (based on the 15610 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 15611 * available to your application. Changes to the URL in the address bar are reflected into 15612 * $location service and changes to $location are reflected into the browser address bar. 15613 * 15614 * **The $location service:** 15615 * 15616 * - Exposes the current URL in the browser address bar, so you can 15617 * - Watch and observe the URL. 15618 * - Change the URL. 15619 * - Synchronizes the URL with the browser when the user 15620 * - Changes the address bar. 15621 * - Clicks the back or forward button (or clicks a History link). 15622 * - Clicks on a link. 15623 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 15624 * 15625 * For more information see {@link guide/$location Developer Guide: Using $location} 15626 */ 15627 15628/** 15629 * @ngdoc provider 15630 * @name $locationProvider 15631 * @description 15632 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 15633 */ 15634function $LocationProvider() { 15635 var hashPrefix = '', 15636 html5Mode = { 15637 enabled: false, 15638 requireBase: true, 15639 rewriteLinks: true 15640 }; 15641 15642 /** 15643 * @ngdoc method 15644 * @name $locationProvider#hashPrefix 15645 * @description 15646 * @param {string=} prefix Prefix for hash part (containing path and search) 15647 * @returns {*} current value if used as getter or itself (chaining) if used as setter 15648 */ 15649 this.hashPrefix = function(prefix) { 15650 if (isDefined(prefix)) { 15651 hashPrefix = prefix; 15652 return this; 15653 } else { 15654 return hashPrefix; 15655 } 15656 }; 15657 15658 /** 15659 * @ngdoc method 15660 * @name $locationProvider#html5Mode 15661 * @description 15662 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 15663 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 15664 * properties: 15665 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 15666 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 15667 * support `pushState`. 15668 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 15669 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 15670 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 15671 * See the {@link guide/$location $location guide for more information} 15672 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 15673 * enables/disables url rewriting for relative links. 15674 * 15675 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 15676 */ 15677 this.html5Mode = function(mode) { 15678 if (isBoolean(mode)) { 15679 html5Mode.enabled = mode; 15680 return this; 15681 } else if (isObject(mode)) { 15682 15683 if (isBoolean(mode.enabled)) { 15684 html5Mode.enabled = mode.enabled; 15685 } 15686 15687 if (isBoolean(mode.requireBase)) { 15688 html5Mode.requireBase = mode.requireBase; 15689 } 15690 15691 if (isBoolean(mode.rewriteLinks)) { 15692 html5Mode.rewriteLinks = mode.rewriteLinks; 15693 } 15694 15695 return this; 15696 } else { 15697 return html5Mode; 15698 } 15699 }; 15700 15701 /** 15702 * @ngdoc event 15703 * @name $location#$locationChangeStart 15704 * @eventType broadcast on root scope 15705 * @description 15706 * Broadcasted before a URL will change. 15707 * 15708 * This change can be prevented by calling 15709 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 15710 * details about event object. Upon successful change 15711 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 15712 * 15713 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 15714 * the browser supports the HTML5 History API. 15715 * 15716 * @param {Object} angularEvent Synthetic event object. 15717 * @param {string} newUrl New URL 15718 * @param {string=} oldUrl URL that was before it was changed. 15719 * @param {string=} newState New history state object 15720 * @param {string=} oldState History state object that was before it was changed. 15721 */ 15722 15723 /** 15724 * @ngdoc event 15725 * @name $location#$locationChangeSuccess 15726 * @eventType broadcast on root scope 15727 * @description 15728 * Broadcasted after a URL was changed. 15729 * 15730 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 15731 * the browser supports the HTML5 History API. 15732 * 15733 * @param {Object} angularEvent Synthetic event object. 15734 * @param {string} newUrl New URL 15735 * @param {string=} oldUrl URL that was before it was changed. 15736 * @param {string=} newState New history state object 15737 * @param {string=} oldState History state object that was before it was changed. 15738 */ 15739 15740 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 15741 function($rootScope, $browser, $sniffer, $rootElement, $window) { 15742 var $location, 15743 LocationMode, 15744 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 15745 initialUrl = $browser.url(), 15746 appBase; 15747 15748 if (html5Mode.enabled) { 15749 if (!baseHref && html5Mode.requireBase) { 15750 throw $locationMinErr('nobase', 15751 "$location in HTML5 mode requires a <base> tag to be present!"); 15752 } 15753 appBase = serverBase(initialUrl) + (baseHref || '/'); 15754 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 15755 } else { 15756 appBase = stripHash(initialUrl); 15757 LocationMode = LocationHashbangUrl; 15758 } 15759 var appBaseNoFile = stripFile(appBase); 15760
15761 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 15762 $location.$$parseLinkUrl(initialUrl, initialUrl); 15763 15764 $location.$$state = $browser.state(); 15765 15766 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 15767 15768 function setBrowserUrlWithFallback(url, replace, state) { 15769 var oldUrl = $location.url(); 15770 var oldState = $location.$$state; 15771 try { 15772 $browser.url(url, replace, state); 15773 15774 // Make sure $location.state() returns referentially identical (not just deeply equal) 15775 // state object; this makes possible quick checking if the state changed in the digest 15776 // loop. Checking deep equality would be too expensive. 15777 $location.$$state = $browser.state(); 15778 } catch (e) { 15779 // Restore old values if pushState fails 15780 $location.url(oldUrl); 15781 $location.$$state = oldState; 15782 15783 throw e; 15784 } 15785 } 15786 15787 $rootElement.on('click', function(event) { 15788 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 15789 // currently we open nice url link and redirect then 15790 15791 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 15792 15793 var elm = jqLite(event.target); 15794 15795 // traverse the DOM up to find first A tag 15796 while (nodeName_(elm[0]) !== 'a') { 15797 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 15798 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 15799 } 15800 15801 var absHref = elm.prop('href'); 15802 // get the actual href attribute - see 15803 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 15804 var relHref = elm.attr('href') || elm.attr('xlink:href'); 15805 15806 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 15807 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 15808 // an animation. 15809 absHref = urlResolve(absHref.animVal).href; 15810 } 15811 15812 // Ignore when url is started with javascript: or mailto: 15813 if (IGNORE_URI_REGEXP.test(absHref)) return; 15814 15815 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 15816 if ($location.$$parseLinkUrl(absHref, relHref)) { 15817 // We do a preventDefault for all urls that are part of the angular application, 15818 // in html5mode and also without, so that we are able to abort navigation without 15819 // getting double entries in the location history. 15820 event.preventDefault(); 15821 // update location manually 15822 if ($location.absUrl() != $browser.url()) { 15823 $rootScope.$apply(); 15824 // hack to work around FF6 bug 684208 when scenario runner clicks on links 15825 $window.angular['ff-684208-preventDefault'] = true; 15826 } 15827 } 15828 } 15829 }); 15830 15831 15832 // rewrite hashbang url <> html5 url 15833 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 15834 $browser.url($location.absUrl(), true); 15835 } 15836 15837 var initializing = true; 15838 15839 // update $location when $browser url changes 15840 $browser.onUrlChange(function(newUrl, newState) { 15841 15842 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { 15843 // If we are navigating outside of the app then force a reload 15844 $window.location.href = newUrl; 15845 return; 15846 } 15847 15848 $rootScope.$evalAsync(function() { 15849 var oldUrl = $location.absUrl(); 15850 var oldState = $location.$$state; 15851 var defaultPrevented; 15852 newUrl = trimEmptyHash(newUrl); 15853 $location.$$parse(newUrl); 15854 $location.$$state = newState; 15855 15856 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 15857 newState, oldState).defaultPrevented; 15858 15859 // if the location was changed by a `$locationChangeStart` handler then stop 15860 // processing this location change 15861 if ($location.absUrl() !== newUrl) return; 15862 15863 if (defaultPrevented) { 15864 $location.$$parse(oldUrl); 15865 $location.$$state = oldState; 15866 setBrowserUrlWithFallback(oldUrl, false, oldState); 15867 } else { 15868 initializing = false;
15869 afterLocationChange(oldUrl, oldState); 15870 } 15871 }); 15872 if (!$rootScope.$$phase) $rootScope.$digest(); 15873 }); 15874 15875 // update browser 15876 $rootScope.$watch(function $locationWatch() { 15877 var oldUrl = trimEmptyHash($browser.url()); 15878 var newUrl = trimEmptyHash($location.absUrl()); 15879 var oldState = $browser.state(); 15880 var currentReplace = $location.$$replace; 15881 var urlOrStateChanged = oldUrl !== newUrl || 15882 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 15883 15884 if (initializing || urlOrStateChanged) { 15885 initializing = false; 15886 15887 $rootScope.$evalAsync(function() { 15888 var newUrl = $location.absUrl(); 15889 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 15890 $location.$$state, oldState).defaultPrevented; 15891 15892 // if the location was changed by a `$locationChangeStart` handler then stop 15893 // processing this location change 15894 if ($location.absUrl() !== newUrl) return; 15895 15896 if (defaultPrevented) { 15897 $location.$$parse(oldUrl); 15898 $location.$$state = oldState; 15899 } else { 15900 if (urlOrStateChanged) { 15901 setBrowserUrlWithFallback(newUrl, currentReplace, 15902 oldState === $location.$$state ? null : $location.$$state); 15903 } 15904 afterLocationChange(oldUrl, oldState); 15905 } 15906 }); 15907 } 15908 15909 $location.$$replace = false; 15910 15911 // we don't need to return anything because $evalAsync will make the digest loop dirty when 15912 // there is a change 15913 }); 15914 15915 return $location; 15916 15917 function afterLocationChange(oldUrl, oldState) { 15918 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 15919 $location.$$state, oldState); 15920 } 15921}]; 15922} 15923 15924/** 15925 * @ngdoc service 15926 * @name $log 15927 * @requires $window 15928 * 15929 * @description 15930 * Simple service for logging. Default implementation safely writes the message 15931 * into the browser's console (if present). 15932 * 15933 * The main purpose of this service is to simplify debugging and troubleshooting. 15934 * 15935 * The default is to log `debug` messages. You can use 15936 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 15937 * 15938 * @example 15939 <example module="logExample"> 15940 <file name="script.js"> 15941 angular.module('logExample', []) 15942 .controller('LogController', ['$scope', '$log', function($scope, $log) { 15943 $scope.$log = $log; 15944 $scope.message = 'Hello World!'; 15945 }]); 15946 </file> 15947 <file name="index.html"> 15948 <div ng-controller="LogController"> 15949 <p>Reload this page with open console, enter text and hit the log button...</p> 15950 <label>Message: 15951 <input type="text" ng-model="message" /></label> 15952 <button ng-click="$log.log(message)">log</button> 15953 <button ng-click="$log.warn(message)">warn</button> 15954 <button ng-click="$log.info(message)">info</button> 15955 <button ng-click="$log.error(message)">error</button> 15956 <button ng-click="$log.debug(message)">debug</button> 15957 </div> 15958 </file> 15959 </example> 15960 */ 15961 15962/** 15963 * @ngdoc provider 15964 * @name $logProvider 15965 * @description 15966 * Use the `$logProvider` to configure how the application logs messages 15967 */ 15968function $LogProvider() { 15969 var debug = true, 15970 self = this; 15971 15972 /** 15973 * @ngdoc method 15974 * @name $logProvider#debugEnabled 15975 * @description 15976 * @param {boolean=} flag enable or disable debug level messages 15977 * @returns {*} current value if used as getter or itself (chaining) if used as setter 15978 */ 15979 this.debugEnabled = function(flag) { 15980 if (isDefined(flag)) { 15981 debug = flag; 15982 return this; 15983 } else { 15984 return debug; 15985 } 15986 }; 15987 15988 this.$get = ['$window', function($window) { 15989 return { 15990 /** 15991 * @ngdoc method 15992 * @name $log#log 15993 * 15994 * @description 15995 * Write a log message 15996 */ 15997 log: consoleLog('log'), 15998 15999 /** 16000 * @ngdoc method 16001 * @name $log#info 16002 * 16003 * @description 16004 * Write an information message 16005 */ 16006 info: consoleLog('info'), 16007 16008 /** 16009 * @ngdoc method 16010 * @name $log#warn 16011 *
16012 * @description 16013 * Write a warning message 16014 */ 16015 warn: consoleLog('warn'), 16016 16017 /** 16018 * @ngdoc method 16019 * @name $log#error 16020 * 16021 * @description 16022 * Write an error message 16023 */ 16024 error: consoleLog('error'), 16025 16026 /** 16027 * @ngdoc method 16028 * @name $log#debug 16029 * 16030 * @description 16031 * Write a debug message 16032 */ 16033 debug: (function() { 16034 var fn = consoleLog('debug'); 16035 16036 return function() { 16037 if (debug) { 16038 fn.apply(self, arguments); 16039 } 16040 }; 16041 }()) 16042 }; 16043 16044 function formatError(arg) { 16045 if (arg instanceof Error) { 16046 if (arg.stack) { 16047 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 16048 ? 'Error: ' + arg.message + '\n' + arg.stack 16049 : arg.stack; 16050 } else if (arg.sourceURL) { 16051 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 16052 } 16053 } 16054 return arg; 16055 } 16056 16057 function consoleLog(type) { 16058 var console = $window.console || {}, 16059 logFn = console[type] || console.log || noop, 16060 hasApply = false; 16061 16062 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 16063 // The reason behind this is that console.log has type "object" in IE8... 16064 try { 16065 hasApply = !!logFn.apply; 16066 } catch (e) {} 16067 16068 if (hasApply) { 16069 return function() { 16070 var args = []; 16071 forEach(arguments, function(arg) { 16072 args.push(formatError(arg)); 16073 }); 16074 return logFn.apply(console, args); 16075 }; 16076 } 16077 16078 // we are IE which either doesn't have window.console => this is noop and we do nothing, 16079 // or we are IE where console.log doesn't have apply so we log at least first 2 args 16080 return function(arg1, arg2) { 16081 logFn(arg1, arg2 == null ? '' : arg2); 16082 }; 16083 } 16084 }]; 16085} 16086 16087/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16088 * Any commits to this file should be reviewed with security in mind. * 16089 * Changes to this file can potentially create security vulnerabilities. * 16090 * An approval from 2 Core members with history of modifying * 16091 * this file is required. * 16092 * * 16093 * Does the change somehow allow for arbitrary javascript to be executed? * 16094 * Or allows for someone to change the prototype of built-in objects? * 16095 * Or gives undesired access to variables likes document or window? * 16096 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 16097 16098var $parseMinErr = minErr('$parse'); 16099 16100// Sandboxing Angular Expressions 16101// ------------------------------ 16102// Angular expressions are generally considered safe because these expressions only have direct 16103// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 16104// obtaining a reference to native JS functions such as the Function constructor. 16105// 16106// As an example, consider the following Angular expression: 16107// 16108// {}.toString.constructor('alert("evil JS code")') 16109// 16110// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 16111// against the expression language, but not to prevent exploits that were enabled by exposing 16112// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 16113// practice and therefore we are not even trying to protect against interaction with an object 16114// explicitly exposed in this way. 16115// 16116// In general, it is not possible to access a Window object from an angular expression unless a 16117// window or some DOM object that has a reference to window is published onto a Scope. 16118// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 16119// native objects. 16120// 16121// See https://docs.angularjs.org/guide/security 16122 16123 16124function ensureSafeMemberName(name, fullExpression) { 16125 if (name === "__defineGetter__" || name === "__defineSetter__" 16126 || name === "__lookupGetter__" || name === "__lookupSetter__" 16127 || name === "__proto__") { 16128 throw $parseMinErr('isecfld', 16129 'Attempting to access a disallowed field in Angular expressions! ' 16130 + 'Expression: {0}', fullExpression); 16131 } 16132 return name; 16133} 16134 16135function getStringValue(name) { 16136 // Property names must be strings. This means that non-string objects cannot be used 16137 // as keys in an object. Any non-string object, including a number, is typecasted 16138 // into a string via the toString method. 16139 // -- MDN, https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Property_accessors#Property_names 16140 // 16141 // So, to ensure that we are checking the same `name` that JavaScript would use, we cast it 16142 // to a string. It's not always possible. If `name` is an object and its `toString` method is 16143 // 'broken' (doesn't return a string, isn't a function, etc.), an error will be thrown: 16144 // 16145 // TypeError: Cannot convert object to primitive value 16146 // 16147 // For performance reasons, we don't catch this error here and allow it to propagate up the call 16148 // stack. Note that you'll get the same error in JavaScript if you try to access a property using 16149 // such a 'broken' object as a key. 16150 return name + ''; 16151} 16152 16153function ensureSafeObject(obj, fullExpression) { 16154 // nifty check if obj is Function that is fast and works across iframes and other contexts 16155 if (obj) { 16156 if (obj.constructor === obj) { 16157 throw $parseMinErr('isecfn', 16158 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 16159 fullExpression); 16160 } else if (// isWindow(obj) 16161 obj.window === obj) { 16162 throw $parseMinErr('isecwindow', 16163 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 16164 fullExpression); 16165 } else if (// isElement(obj) 16166 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 16167 throw $parseMinErr('isecdom', 16168 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 16169 fullExpression); 16170 } else if (// block Object so that we can't get hold of dangerous Object.* methods 16171 obj === Object) { 16172 throw $parseMinErr('isecobj', 16173 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 16174 fullExpression); 16175 } 16176 } 16177 return obj; 16178} 16179 16180var CALL = Function.prototype.call; 16181var APPLY = Function.prototype.apply; 16182var BIND = Function.prototype.bind; 16183 16184function ensureSafeFunction(obj, fullExpression) { 16185 if (obj) { 16186 if (obj.constructor === obj) { 16187 throw $parseMinErr('isecfn', 16188 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 16189 fullExpression); 16190 } else if (obj === CALL || obj === APPLY || obj === BIND) { 16191 throw $parseMinErr('isecff', 16192 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 16193 fullExpression); 16194 } 16195 } 16196} 16197 16198function ensureSafeAssignContext(obj, fullExpression) { 16199 if (obj) { 16200 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor || 16201 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) { 16202 throw $parseMinErr('isecaf', 16203 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression); 16204 } 16205 } 16206} 16207 16208var OPERATORS = createMap(); 16209forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 16210var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 16211 16212
vendor: 49,739 bytes, lines 16213-17780
16213///////////////////////////////////////// 16214 16215 16216/** 16217 * @constructor 16218 */ 16219var Lexer = function(options) { 16220 this.options = options; 16221}; 16222 16223Lexer.prototype = { 16224 constructor: Lexer, 16225 16226 lex: function(text) { 16227 this.text = text; 16228 this.index = 0; 16229 this.tokens = []; 16230 16231 while (this.index < this.text.length) { 16232 var ch = this.text.charAt(this.index); 16233 if (ch === '"' || ch === "'") { 16234 this.readString(ch); 16235 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 16236 this.readNumber(); 16237 } else if (this.isIdentifierStart(this.peekMultichar())) { 16238 this.readIdent(); 16239 } else if (this.is(ch, '(){}[].,;:?')) { 16240 this.tokens.push({index: this.index, text: ch}); 16241 this.index++; 16242 } else if (this.isWhitespace(ch)) { 16243 this.index++; 16244 } else { 16245 var ch2 = ch + this.peek(); 16246 var ch3 = ch2 + this.peek(2); 16247 var op1 = OPERATORS[ch]; 16248 var op2 = OPERATORS[ch2]; 16249 var op3 = OPERATORS[ch3]; 16250 if (op1 || op2 || op3) { 16251 var token = op3 ? ch3 : (op2 ? ch2 : ch); 16252 this.tokens.push({index: this.index, text: token, operator: true}); 16253 this.index += token.length; 16254 } else { 16255 this.throwError('Unexpected next character ', this.index, this.index + 1); 16256 } 16257 } 16258 } 16259 return this.tokens; 16260 }, 16261 16262 is: function(ch, chars) { 16263 return chars.indexOf(ch) !== -1; 16264 }, 16265 16266 peek: function(i) { 16267 var num = i || 1; 16268 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 16269 }, 16270 16271 isNumber: function(ch) { 16272 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 16273 }, 16274 16275 isWhitespace: function(ch) { 16276 // IE treats non-breaking space as \u00A0 16277 return (ch === ' ' || ch === '\r' || ch === '\t' || 16278 ch === '\n' || ch === '\v' || ch === '\u00A0'); 16279 }, 16280 16281 isIdentifierStart: function(ch) { 16282 return this.options.isIdentifierStart ? 16283 this.options.isIdentifierStart(ch, this.codePointAt(ch)) : 16284 this.isValidIdentifierStart(ch); 16285 }, 16286 16287 isValidIdentifierStart: function(ch) { 16288 return ('a' <= ch && ch <= 'z' || 16289 'A' <= ch && ch <= 'Z' || 16290 '_' === ch || ch === '$'); 16291 }, 16292 16293 isIdentifierContinue: function(ch) { 16294 return this.options.isIdentifierContinue ? 16295 this.options.isIdentifierContinue(ch, this.codePointAt(ch)) : 16296 this.isValidIdentifierContinue(ch); 16297 }, 16298 16299 isValidIdentifierContinue: function(ch, cp) { 16300 return this.isValidIdentifierStart(ch, cp) || this.isNumber(ch); 16301 }, 16302 16303 codePointAt: function(ch) { 16304 if (ch.length === 1) return ch.charCodeAt(0); 16305 /*jshint bitwise: false*/ 16306 return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35FDC00; 16307 /*jshint bitwise: true*/ 16308 }, 16309 16310 peekMultichar: function() { 16311 var ch = this.text.charAt(this.index); 16312 var peek = this.peek(); 16313 if (!peek) { 16314 return ch; 16315 } 16316 var cp1 = ch.charCodeAt(0); 16317 var cp2 = peek.charCodeAt(0); 16318 if (cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF) { 16319 return ch + peek; 16320 } 16321 return ch; 16322 }, 16323 16324 isExpOperator: function(ch) { 16325 return (ch === '-' || ch === '+' || this.isNumber(ch)); 16326 }, 16327 16328 throwError: function(error, start, end) { 16329 end = end || this.index; 16330 var colStr = (isDefined(start) 16331 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 16332 : ' ' + end); 16333 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 16334 error, colStr, this.text); 16335 }, 16336 16337 readNumber: function() { 16338 var number = ''; 16339 var start = this.index; 16340 while (this.index < this.text.length) { 16341 var ch = lowercase(this.text.charAt(this.index)); 16342 if (ch == '.' || this.isNumber(ch)) { 16343 number += ch; 16344 } else { 16345 var peekCh = this.peek(); 16346 if (ch == 'e' && this.isExpOperator(peekCh)) { 16347 number += ch; 16348 } else if (this.isExpOperator(ch) && 16349 peekCh && this.isNumber(peekCh) && 16350 number.charAt(number.length - 1) == 'e') { 16351 number += ch; 16352 } else if (this.isExpOperator(ch) && 16353 (!peekCh || !this.isNumber(peekCh)) && 16354 number.charAt(number.length - 1) == 'e') { 16355 this.throwError('Invalid exponent'); 16356 } else { 16357 break; 16358 } 16359 } 16360 this.index++; 16361 } 16362 this.tokens.push({ 16363 index: start, 16364 text: number, 16365 constant: true, 16366 value: Number(number) 16367 }); 16368 }, 16369 16370 readIdent: function() { 16371 var start = this.index; 16372 this.index += this.peekMultichar().length; 16373 while (this.index < this.text.length) { 16374 var ch = this.peekMultichar(); 16375 if (!this.isIdentifierContinue(ch)) { 16376 break; 16377 } 16378 this.index += ch.length; 16379 } 16380 this.tokens.push({ 16381 index: start, 16382 text: this.text.slice(start, this.index), 16383 identifier: true 16384 }); 16385 }, 16386 16387 readString: function(quote) { 16388 var start = this.index; 16389 this.index++; 16390 var string = ''; 16391 var rawString = quote; 16392 var escape = false; 16393 while (this.index < this.text.length) { 16394 var ch = this.text.charAt(this.index); 16395 rawString += ch; 16396 if (escape) { 16397 if (ch === 'u') { 16398 var hex = this.text.substring(this.index + 1, this.index + 5); 16399 if (!hex.match(/[\da-f]{4}/i)) { 16400 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 16401 } 16402 this.index += 4; 16403 string += String.fromCharCode(parseInt(hex, 16)); 16404 } else { 16405 var rep = ESCAPE[ch]; 16406 string = string + (rep || ch); 16407 } 16408 escape = false; 16409 } else if (ch === '\\') { 16410 escape = true; 16411 } else if (ch === quote) { 16412 this.index++; 16413 this.tokens.push({ 16414 index: start, 16415 text: rawString, 16416 constant: true, 16417 value: string 16418 }); 16419 return; 16420 } else { 16421 string += ch; 16422 } 16423 this.index++; 16424 } 16425 this.throwError('Unterminated quote', start); 16426 } 16427}; 16428 16429var AST = function(lexer, options) { 16430 this.lexer = lexer; 16431 this.options = options; 16432}; 16433 16434AST.Program = 'Program'; 16435AST.ExpressionStatement = 'ExpressionStatement'; 16436AST.AssignmentExpression = 'AssignmentExpression'; 16437AST.ConditionalExpression = 'ConditionalExpression'; 16438AST.LogicalExpression = 'LogicalExpression'; 16439AST.BinaryExpression = 'BinaryExpression'; 16440AST.UnaryExpression = 'UnaryExpression'; 16441AST.CallExpression = 'CallExpression'; 16442AST.MemberExpression = 'MemberExpression'; 16443AST.Identifier = 'Identifier'; 16444AST.Literal = 'Literal'; 16445AST.ArrayExpression = 'ArrayExpression'; 16446AST.Property = 'Property'; 16447AST.ObjectExpression = 'ObjectExpression'; 16448AST.ThisExpression = 'ThisExpression'; 16449AST.LocalsExpression = 'LocalsExpression'; 16450 16451// Internal use only 16452AST.NGValueParameter = 'NGValueParameter'; 16453 16454AST.prototype = { 16455 ast: function(text) { 16456 this.text = text; 16457 this.tokens = this.lexer.lex(text); 16458 16459 var value = this.program(); 16460 16461 if (this.tokens.length !== 0) { 16462 this.throwError('is an unexpected token', this.tokens[0]); 16463 } 16464 16465 return value; 16466 }, 16467 16468 program: function() { 16469 var body = []; 16470 while (true) { 16471 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 16472 body.push(this.expressionStatement()); 16473 if (!this.expect(';')) { 16474 return { type: AST.Program, body: body}; 16475 } 16476 } 16477 }, 16478 16479 expressionStatement: function() { 16480 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 16481 }, 16482 16483 filterChain: function() { 16484 var left = this.expression(); 16485 var token; 16486 while ((token = this.expect('|'))) { 16487 left = this.filter(left); 16488 } 16489 return left; 16490 }, 16491 16492 expression: function() { 16493 return this.assignment(); 16494 }, 16495 16496 assignment: function() { 16497 var result = this.ternary(); 16498 if (this.expect('=')) { 16499 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 16500 } 16501 return result; 16502 }, 16503 16504 ternary: function() { 16505 var test = this.logicalOR(); 16506 var alternate; 16507 var consequent; 16508 if (this.expect('?')) { 16509 alternate = this.expression(); 16510 if (this.consume(':')) { 16511 consequent = this.expression(); 16512 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 16513 } 16514 } 16515 return test; 16516 }, 16517 16518 logicalOR: function() { 16519 var left = this.logicalAND(); 16520 while (this.expect('||')) { 16521 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 16522 } 16523 return left; 16524 }, 16525 16526 logicalAND: function() { 16527 var left = this.equality(); 16528 while (this.expect('&&')) { 16529 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 16530 } 16531 return left; 16532 }, 16533 16534 equality: function() { 16535 var left = this.relational(); 16536 var token; 16537 while ((token = this.expect('==','!=','===','!=='))) { 16538 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 16539 } 16540 return left; 16541 }, 16542 16543 relational: function() { 16544 var left = this.additive(); 16545 var token; 16546 while ((token = this.expect('<', '>', '<=', '>='))) { 16547 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 16548 } 16549 return left; 16550 }, 16551 16552 additive: function() { 16553 var left = this.multiplicative(); 16554 var token; 16555 while ((token = this.expect('+','-'))) { 16556 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 16557 } 16558 return left; 16559 }, 16560 16561 multiplicative: function() { 16562 var left = this.unary(); 16563 var token; 16564 while ((token = this.expect('*','/','%'))) { 16565 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 16566 } 16567 return left; 16568 }, 16569 16570 unary: function() { 16571 var token; 16572 if ((token = this.expect('+', '-', '!'))) { 16573 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 16574 } else { 16575 return this.primary(); 16576 } 16577 }, 16578 16579 primary: function() { 16580 var primary; 16581 if (this.expect('(')) { 16582 primary = this.filterChain(); 16583 this.consume(')'); 16584 } else if (this.expect('[')) { 16585 primary = this.arrayDeclaration(); 16586 } else if (this.expect('{')) { 16587 primary = this.object(); 16588 } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { 16589 primary = copy(this.selfReferential[this.consume().text]); 16590 } else if (this.options.literals.hasOwnProperty(this.peek().text)) { 16591 primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; 16592 } else if (this.peek().identifier) { 16593 primary = this.identifier(); 16594 } else if (this.peek().constant) { 16595 primary = this.constant(); 16596 } else { 16597 this.throwError('not a primary expression', this.peek()); 16598 } 16599 16600 var next; 16601 while ((next = this.expect('(', '[', '.'))) { 16602 if (next.text === '(') { 16603 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 16604 this.consume(')'); 16605 } else if (next.text === '[') { 16606 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 16607 this.consume(']'); 16608 } else if (next.text === '.') { 16609 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 16610 } else { 16611 this.throwError('IMPOSSIBLE'); 16612 } 16613 } 16614 return primary; 16615 }, 16616 16617 filter: function(baseExpression) { 16618 var args = [baseExpression]; 16619 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 16620 16621 while (this.expect(':')) { 16622 args.push(this.expression()); 16623 } 16624 16625 return result; 16626 }, 16627 16628 parseArguments: function() { 16629 var args = []; 16630 if (this.peekToken().text !== ')') { 16631 do { 16632 args.push(this.expression()); 16633 } while (this.expect(',')); 16634 } 16635 return args; 16636 }, 16637 16638 identifier: function() { 16639 var token = this.consume(); 16640 if (!token.identifier) { 16641 this.throwError('is not a valid identifier', token); 16642 } 16643 return { type: AST.Identifier, name: token.text }; 16644 }, 16645 16646 constant: function() { 16647 // TODO check that it is a constant 16648 return { type: AST.Literal, value: this.consume().value }; 16649 }, 16650 16651 arrayDeclaration: function() { 16652 var elements = []; 16653 if (this.peekToken().text !== ']') { 16654 do { 16655 if (this.peek(']')) { 16656 // Support trailing commas per ES5.1. 16657 break; 16658 } 16659 elements.push(this.expression()); 16660 } while (this.expect(',')); 16661 } 16662 this.consume(']'); 16663 16664 return { type: AST.ArrayExpression, elements: elements }; 16665 }, 16666 16667 object: function() { 16668 var properties = [], property; 16669 if (this.peekToken().text !== '}') { 16670 do { 16671 if (this.peek('}')) { 16672 // Support trailing commas per ES5.1. 16673 break; 16674 } 16675 property = {type: AST.Property, kind: 'init'}; 16676 if (this.peek().constant) { 16677 property.key = this.constant(); 16678 } else if (this.peek().identifier) { 16679 property.key = this.identifier(); 16680 } else { 16681 this.throwError("invalid key", this.peek()); 16682 } 16683 this.consume(':'); 16684 property.value = this.expression(); 16685 properties.push(property); 16686 } while (this.expect(',')); 16687 } 16688 this.consume('}'); 16689 16690 return {type: AST.ObjectExpression, properties: properties }; 16691 }, 16692 16693 throwError: function(msg, token) { 16694 throw $parseMinErr('syntax', 16695 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 16696 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 16697 }, 16698 16699 consume: function(e1) { 16700 if (this.tokens.length === 0) { 16701 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 16702 } 16703 16704 var token = this.expect(e1); 16705 if (!token) { 16706 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 16707 } 16708 return token; 16709 }, 16710 16711 peekToken: function() { 16712 if (this.tokens.length === 0) { 16713 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 16714 } 16715 return this.tokens[0]; 16716 }, 16717 16718 peek: function(e1, e2, e3, e4) { 16719 return this.peekAhead(0, e1, e2, e3, e4); 16720 }, 16721 16722 peekAhead: function(i, e1, e2, e3, e4) { 16723 if (this.tokens.length > i) { 16724 var token = this.tokens[i]; 16725 var t = token.text; 16726 if (t === e1 || t === e2 || t === e3 || t === e4 || 16727 (!e1 && !e2 && !e3 && !e4)) { 16728 return token; 16729 } 16730 } 16731 return false; 16732 }, 16733 16734 expect: function(e1, e2, e3, e4) { 16735 var token = this.peek(e1, e2, e3, e4); 16736 if (token) { 16737 this.tokens.shift(); 16738 return token; 16739 } 16740 return false; 16741 }, 16742 16743 selfReferential: { 16744 'this': {type: AST.ThisExpression }, 16745 '$locals': {type: AST.LocalsExpression } 16746 } 16747}; 16748 16749function ifDefined(v, d) { 16750 return typeof v !== 'undefined' ? v : d; 16751} 16752 16753function plusFn(l, r) { 16754 if (typeof l === 'undefined') return r; 16755 if (typeof r === 'undefined') return l; 16756 return l + r; 16757} 16758 16759function isStateless($filter, filterName) { 16760 var fn = $filter(filterName); 16761 return !fn.$stateful; 16762} 16763 16764function findConstantAndWatchExpressions(ast, $filter) { 16765 var allConstants; 16766 var argsToWatch; 16767 switch (ast.type) { 16768 case AST.Program: 16769 allConstants = true; 16770 forEach(ast.body, function(expr) { 16771 findConstantAndWatchExpressions(expr.expression, $filter); 16772 allConstants = allConstants && expr.expression.constant; 16773 }); 16774 ast.constant = allConstants; 16775 break; 16776 case AST.Literal: 16777 ast.constant = true; 16778 ast.toWatch = []; 16779 break; 16780 case AST.UnaryExpression: 16781 findConstantAndWatchExpressions(ast.argument, $filter); 16782 ast.constant = ast.argument.constant; 16783 ast.toWatch = ast.argument.toWatch; 16784 break; 16785 case AST.BinaryExpression: 16786 findConstantAndWatchExpressions(ast.left, $filter); 16787 findConstantAndWatchExpressions(ast.right, $filter); 16788 ast.constant = ast.left.constant && ast.right.constant; 16789 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 16790 break; 16791 case AST.LogicalExpression: 16792 findConstantAndWatchExpressions(ast.left, $filter); 16793 findConstantAndWatchExpressions(ast.right, $filter); 16794 ast.constant = ast.left.constant && ast.right.constant; 16795 ast.toWatch = ast.constant ? [] : [ast]; 16796 break; 16797 case AST.ConditionalExpression: 16798 findConstantAndWatchExpressions(ast.test, $filter); 16799 findConstantAndWatchExpressions(ast.alternate, $filter); 16800 findConstantAndWatchExpressions(ast.consequent, $filter); 16801 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 16802 ast.toWatch = ast.constant ? [] : [ast]; 16803 break; 16804 case AST.Identifier: 16805 ast.constant = false; 16806 ast.toWatch = [ast]; 16807 break; 16808 case AST.MemberExpression: 16809 findConstantAndWatchExpressions(ast.object, $filter); 16810 if (ast.computed) { 16811 findConstantAndWatchExpressions(ast.property, $filter); 16812 } 16813 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 16814 ast.toWatch = [ast]; 16815 break; 16816 case AST.CallExpression: 16817 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 16818 argsToWatch = []; 16819 forEach(ast.arguments, function(expr) { 16820 findConstantAndWatchExpressions(expr, $filter); 16821 allConstants = allConstants && expr.constant; 16822 if (!expr.constant) { 16823 argsToWatch.push.apply(argsToWatch, expr.toWatch); 16824 } 16825 }); 16826 ast.constant = allConstants; 16827 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 16828 break; 16829 case AST.AssignmentExpression: 16830 findConstantAndWatchExpressions(ast.left, $filter); 16831 findConstantAndWatchExpressions(ast.right, $filter); 16832 ast.constant = ast.left.constant && ast.right.constant; 16833 ast.toWatch = [ast]; 16834 break; 16835 case AST.ArrayExpression: 16836 allConstants = true; 16837 argsToWatch = []; 16838 forEach(ast.elements, function(expr) { 16839 findConstantAndWatchExpressions(expr, $filter); 16840 allConstants = allConstants && expr.constant; 16841 if (!expr.constant) { 16842 argsToWatch.push.apply(argsToWatch, expr.toWatch); 16843 } 16844 }); 16845 ast.constant = allConstants; 16846 ast.toWatch = argsToWatch; 16847 break; 16848 case AST.ObjectExpression: 16849 allConstants = true; 16850 argsToWatch = []; 16851 forEach(ast.properties, function(property) { 16852 findConstantAndWatchExpressions(property.value, $filter); 16853 allConstants = allConstants && property.value.constant; 16854 if (!property.value.constant) { 16855 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 16856 } 16857 }); 16858 ast.constant = allConstants; 16859 ast.toWatch = argsToWatch; 16860 break; 16861 case AST.ThisExpression: 16862 ast.constant = false; 16863 ast.toWatch = []; 16864 break; 16865 case AST.LocalsExpression: 16866 ast.constant = false; 16867 ast.toWatch = []; 16868 break; 16869 } 16870} 16871 16872function getInputs(body) { 16873 if (body.length != 1) return; 16874 var lastExpression = body[0].expression; 16875 var candidate = lastExpression.toWatch; 16876 if (candidate.length !== 1) return candidate; 16877 return candidate[0] !== lastExpression ? candidate : undefined; 16878} 16879 16880function isAssignable(ast) { 16881 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 16882} 16883 16884function assignableAST(ast) { 16885 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 16886 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 16887 } 16888} 16889 16890function isLiteral(ast) { 16891 return ast.body.length === 0 || 16892 ast.body.length === 1 && ( 16893 ast.body[0].expression.type === AST.Literal || 16894 ast.body[0].expression.type === AST.ArrayExpression || 16895 ast.body[0].expression.type === AST.ObjectExpression); 16896} 16897 16898function isConstant(ast) { 16899 return ast.constant; 16900} 16901 16902function ASTCompiler(astBuilder, $filter) { 16903 this.astBuilder = astBuilder; 16904 this.$filter = $filter; 16905} 16906 16907ASTCompiler.prototype = { 16908 compile: function(expression, expensiveChecks) { 16909 var self = this; 16910 var ast = this.astBuilder.ast(expression); 16911 this.state = { 16912 nextId: 0, 16913 filters: {}, 16914 expensiveChecks: expensiveChecks, 16915 fn: {vars: [], body: [], own: {}}, 16916 assign: {vars: [], body: [], own: {}}, 16917 inputs: [] 16918 }; 16919 findConstantAndWatchExpressions(ast, self.$filter); 16920 var extra = ''; 16921 var assignable; 16922 this.stage = 'assign'; 16923 if ((assignable = assignableAST(ast))) { 16924 this.state.computing = 'assign'; 16925 var result = this.nextId(); 16926 this.recurse(assignable, result); 16927 this.return_(result); 16928 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 16929 } 16930 var toWatch = getInputs(ast.body); 16931 self.stage = 'inputs'; 16932 forEach(toWatch, function(watch, key) { 16933 var fnKey = 'fn' + key; 16934 self.state[fnKey] = {vars: [], body: [], own: {}}; 16935 self.state.computing = fnKey; 16936 var intoId = self.nextId(); 16937 self.recurse(watch, intoId); 16938 self.return_(intoId); 16939 self.state.inputs.push(fnKey); 16940 watch.watchId = key; 16941 }); 16942 this.state.computing = 'fn'; 16943 this.stage = 'main'; 16944 this.recurse(ast); 16945 var fnString = 16946 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 16947 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 16948 '"' + this.USE + ' ' + this.STRICT + '";\n' + 16949 this.filterPrefix() + 16950 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 16951 extra + 16952 this.watchFns() + 16953 'return fn;'; 16954 16955 /* jshint -W054 */ 16956 var fn = (new Function('$filter', 16957 'ensureSafeMemberName', 16958 'ensureSafeObject', 16959 'ensureSafeFunction', 16960 'getStringValue', 16961 'ensureSafeAssignContext', 16962 'ifDefined', 16963 'plus', 16964 'text', 16965 fnString))( 16966 this.$filter, 16967 ensureSafeMemberName, 16968 ensureSafeObject, 16969 ensureSafeFunction, 16970 getStringValue, 16971 ensureSafeAssignContext, 16972 ifDefined, 16973 plusFn, 16974 expression); 16975 /* jshint +W054 */ 16976 this.state = this.stage = undefined; 16977 fn.literal = isLiteral(ast); 16978 fn.constant = isConstant(ast); 16979 return fn; 16980 }, 16981 16982 USE: 'use', 16983 16984 STRICT: 'strict', 16985 16986 watchFns: function() { 16987 var result = []; 16988 var fns = this.state.inputs; 16989 var self = this; 16990 forEach(fns, function(name) { 16991 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 16992 }); 16993 if (fns.length) { 16994 result.push('fn.inputs=[' + fns.join(',') + '];'); 16995 } 16996 return result.join(''); 16997 }, 16998 16999 generateFunction: function(name, params) { 17000 return 'function(' + params + '){' + 17001 this.varsPrefix(name) + 17002 this.body(name) + 17003 '};'; 17004 }, 17005 17006 filterPrefix: function() { 17007 var parts = []; 17008 var self = this; 17009 forEach(this.state.filters, function(id, filter) { 17010 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 17011 }); 17012 if (parts.length) return 'var ' + parts.join(',') + ';'; 17013 return ''; 17014 }, 17015 17016 varsPrefix: function(section) { 17017 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 17018 }, 17019 17020 body: function(section) { 17021 return this.state[section].body.join(''); 17022 }, 17023 17024 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 17025 var left, right, self = this, args, expression; 17026 recursionFn = recursionFn || noop; 17027 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 17028 intoId = intoId || this.nextId(); 17029 this.if_('i', 17030 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 17031 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 17032 ); 17033 return; 17034 } 17035 switch (ast.type) { 17036 case AST.Program: 17037 forEach(ast.body, function(expression, pos) { 17038 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 17039 if (pos !== ast.body.length - 1) { 17040 self.current().body.push(right, ';'); 17041 } else { 17042 self.return_(right); 17043 } 17044 }); 17045 break; 17046 case AST.Literal: 17047 expression = this.escape(ast.value); 17048 this.assign(intoId, expression); 17049 recursionFn(expression); 17050 break; 17051 case AST.UnaryExpression: 17052 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 17053 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 17054 this.assign(intoId, expression); 17055 recursionFn(expression); 17056 break; 17057 case AST.BinaryExpression: 17058 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 17059 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 17060 if (ast.operator === '+') { 17061 expression = this.plus(left, right); 17062 } else if (ast.operator === '-') { 17063 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 17064 } else { 17065 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 17066 } 17067 this.assign(intoId, expression); 17068 recursionFn(expression); 17069 break; 17070 case AST.LogicalExpression: 17071 intoId = intoId || this.nextId(); 17072 self.recurse(ast.left, intoId); 17073 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 17074 recursionFn(intoId); 17075 break; 17076 case AST.ConditionalExpression: 17077 intoId = intoId || this.nextId(); 17078 self.recurse(ast.test, intoId); 17079 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 17080 recursionFn(intoId); 17081 break; 17082 case AST.Identifier: 17083 intoId = intoId || this.nextId(); 17084 if (nameId) { 17085 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 17086 nameId.computed = false; 17087 nameId.name = ast.name; 17088 } 17089 ensureSafeMemberName(ast.name); 17090 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 17091 function() { 17092 self.if_(self.stage === 'inputs' || 's', function() { 17093 if (create && create !== 1) { 17094 self.if_( 17095 self.not(self.nonComputedMember('s', ast.name)), 17096 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 17097 } 17098 self.assign(intoId, self.nonComputedMember('s', ast.name)); 17099 }); 17100 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 17101 ); 17102 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 17103 self.addEnsureSafeObject(intoId); 17104 } 17105 recursionFn(intoId); 17106 break; 17107 case AST.MemberExpression: 17108 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 17109 intoId = intoId || this.nextId(); 17110 self.recurse(ast.object, left, undefined, function() { 17111 self.if_(self.notNull(left), function() { 17112 if (create && create !== 1) { 17113 self.addEnsureSafeAssignContext(left); 17114 } 17115 if (ast.computed) { 17116 right = self.nextId(); 17117 self.recurse(ast.property, right); 17118 self.getStringValue(right); 17119 self.addEnsureSafeMemberName(right); 17120 if (create && create !== 1) { 17121 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 17122 } 17123 expression = self.ensureSafeObject(self.computedMember(left, right)); 17124 self.assign(intoId, expression); 17125 if (nameId) { 17126 nameId.computed = true; 17127 nameId.name = right; 17128 } 17129 } else { 17130 ensureSafeMemberName(ast.property.name); 17131 if (create && create !== 1) { 17132 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 17133 } 17134 expression = self.nonComputedMember(left, ast.property.name); 17135 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 17136 expression = self.ensureSafeObject(expression); 17137 } 17138 self.assign(intoId, expression); 17139 if (nameId) { 17140 nameId.computed = false; 17141 nameId.name = ast.property.name; 17142 } 17143 } 17144 }, function() { 17145 self.assign(intoId, 'undefined'); 17146 }); 17147 recursionFn(intoId); 17148 }, !!create); 17149 break; 17150 case AST.CallExpression: 17151 intoId = intoId || this.nextId(); 17152 if (ast.filter) { 17153 right = self.filter(ast.callee.name); 17154 args = []; 17155 forEach(ast.arguments, function(expr) { 17156 var argument = self.nextId(); 17157 self.recurse(expr, argument); 17158 args.push(argument); 17159 }); 17160 expression = right + '(' + args.join(',') + ')'; 17161 self.assign(intoId, expression); 17162 recursionFn(intoId); 17163 } else { 17164 right = self.nextId(); 17165 left = {}; 17166 args = []; 17167 self.recurse(ast.callee, right, left, function() { 17168 self.if_(self.notNull(right), function() { 17169 self.addEnsureSafeFunction(right); 17170 forEach(ast.arguments, function(expr) { 17171 self.recurse(expr, self.nextId(), undefined, function(argument) { 17172 args.push(self.ensureSafeObject(argument)); 17173 }); 17174 }); 17175 if (left.name) { 17176 if (!self.state.expensiveChecks) { 17177 self.addEnsureSafeObject(left.context); 17178 } 17179 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 17180 } else { 17181 expression = right + '(' + args.join(',') + ')'; 17182 } 17183 expression = self.ensureSafeObject(expression); 17184 self.assign(intoId, expression); 17185 }, function() { 17186 self.assign(intoId, 'undefined'); 17187 }); 17188 recursionFn(intoId); 17189 }); 17190 } 17191 break; 17192 case AST.AssignmentExpression: 17193 right = this.nextId(); 17194 left = {}; 17195 if (!isAssignable(ast.left)) { 17196 throw $parseMinErr('lval', 'Trying to assign a value to a non l-value'); 17197 } 17198 this.recurse(ast.left, undefined, left, function() { 17199 self.if_(self.notNull(left.context), function() { 17200 self.recurse(ast.right, right); 17201 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 17202 self.addEnsureSafeAssignContext(left.context); 17203 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 17204 self.assign(intoId, expression); 17205 recursionFn(intoId || expression); 17206 }); 17207 }, 1); 17208 break; 17209 case AST.ArrayExpression: 17210 args = []; 17211 forEach(ast.elements, function(expr) { 17212 self.recurse(expr, self.nextId(), undefined, function(argument) { 17213 args.push(argument); 17214 }); 17215 }); 17216 expression = '[' + args.join(',') + ']'; 17217 this.assign(intoId, expression); 17218 recursionFn(expression); 17219 break; 17220 case AST.ObjectExpression: 17221 args = []; 17222 forEach(ast.properties, function(property) { 17223 self.recurse(property.value, self.nextId(), undefined, function(expr) { 17224 args.push(self.escape( 17225 property.key.type === AST.Identifier ? property.key.name : 17226 ('' + property.key.value)) + 17227 ':' + expr); 17228 }); 17229 }); 17230 expression = '{' + args.join(',') + '}'; 17231 this.assign(intoId, expression); 17232 recursionFn(expression); 17233 break; 17234 case AST.ThisExpression: 17235 this.assign(intoId, 's'); 17236 recursionFn('s'); 17237 break; 17238 case AST.LocalsExpression: 17239 this.assign(intoId, 'l'); 17240 recursionFn('l'); 17241 break; 17242 case AST.NGValueParameter: 17243 this.assign(intoId, 'v'); 17244 recursionFn('v'); 17245 break; 17246 } 17247 }, 17248 17249 getHasOwnProperty: function(element, property) { 17250 var key = element + '.' + property; 17251 var own = this.current().own; 17252 if (!own.hasOwnProperty(key)) { 17253 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 17254 } 17255 return own[key]; 17256 }, 17257 17258 assign: function(id, value) { 17259 if (!id) return; 17260 this.current().body.push(id, '=', value, ';'); 17261 return id; 17262 }, 17263 17264 filter: function(filterName) { 17265 if (!this.state.filters.hasOwnProperty(filterName)) { 17266 this.state.filters[filterName] = this.nextId(true); 17267 } 17268 return this.state.filters[filterName]; 17269 }, 17270 17271 ifDefined: function(id, defaultValue) { 17272 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 17273 }, 17274 17275 plus: function(left, right) { 17276 return 'plus(' + left + ',' + right + ')'; 17277 }, 17278 17279 return_: function(id) { 17280 this.current().body.push('return ', id, ';'); 17281 }, 17282 17283 if_: function(test, alternate, consequent) { 17284 if (test === true) { 17285 alternate(); 17286 } else { 17287 var body = this.current().body; 17288 body.push('if(', test, '){'); 17289 alternate(); 17290 body.push('}'); 17291 if (consequent) { 17292 body.push('else{'); 17293 consequent(); 17294 body.push('}'); 17295 } 17296 } 17297 }, 17298 17299 not: function(expression) { 17300 return '!(' + expression + ')'; 17301 }, 17302 17303 notNull: function(expression) { 17304 return expression + '!=null'; 17305 }, 17306 17307 nonComputedMember: function(left, right) { 17308 var SAFE_IDENTIFIER = /[$_a-zA-Z][$_a-zA-Z0-9]*/; 17309 var UNSAFE_CHARACTERS = /[^$_a-zA-Z0-9]/g; 17310 if (SAFE_IDENTIFIER.test(right)) { 17311 return left + '.' + right; 17312 } else { 17313 return left + '["' + right.replace(UNSAFE_CHARACTERS, this.stringEscapeFn) + '"]'; 17314 } 17315 }, 17316 17317 computedMember: function(left, right) { 17318 return left + '[' + right + ']'; 17319 }, 17320 17321 member: function(left, right, computed) { 17322 if (computed) return this.computedMember(left, right); 17323 return this.nonComputedMember(left, right); 17324 }, 17325 17326 addEnsureSafeObject: function(item) { 17327 this.current().body.push(this.ensureSafeObject(item), ';'); 17328 }, 17329 17330 addEnsureSafeMemberName: function(item) { 17331 this.current().body.push(this.ensureSafeMemberName(item), ';'); 17332 }, 17333 17334 addEnsureSafeFunction: function(item) { 17335 this.current().body.push(this.ensureSafeFunction(item), ';'); 17336 }, 17337 17338 addEnsureSafeAssignContext: function(item) { 17339 this.current().body.push(this.ensureSafeAssignContext(item), ';'); 17340 }, 17341 17342 ensureSafeObject: function(item) { 17343 return 'ensureSafeObject(' + item + ',text)'; 17344 }, 17345 17346 ensureSafeMemberName: function(item) { 17347 return 'ensureSafeMemberName(' + item + ',text)'; 17348 }, 17349 17350 ensureSafeFunction: function(item) { 17351 return 'ensureSafeFunction(' + item + ',text)'; 17352 }, 17353 17354 getStringValue: function(item) { 17355 this.assign(item, 'getStringValue(' + item + ')'); 17356 }, 17357 17358 ensureSafeAssignContext: function(item) { 17359 return 'ensureSafeAssignContext(' + item + ',text)'; 17360 }, 17361 17362 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 17363 var self = this; 17364 return function() { 17365 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 17366 }; 17367 }, 17368 17369 lazyAssign: function(id, value) { 17370 var self = this; 17371 return function() { 17372 self.assign(id, value); 17373 }; 17374 }, 17375 17376 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 17377 17378 stringEscapeFn: function(c) { 17379 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 17380 }, 17381 17382 escape: function(value) { 17383 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 17384 if (isNumber(value)) return value.toString(); 17385 if (value === true) return 'true'; 17386 if (value === false) return 'false'; 17387 if (value === null) return 'null'; 17388 if (typeof value === 'undefined') return 'undefined'; 17389 17390 throw $parseMinErr('esc', 'IMPOSSIBLE'); 17391 }, 17392 17393 nextId: function(skip, init) { 17394 var id = 'v' + (this.state.nextId++); 17395 if (!skip) { 17396 this.current().vars.push(id + (init ? '=' + init : '')); 17397 } 17398 return id; 17399 }, 17400 17401 current: function() { 17402 return this.state[this.state.computing]; 17403 } 17404}; 17405 17406 17407function ASTInterpreter(astBuilder, $filter) { 17408 this.astBuilder = astBuilder; 17409 this.$filter = $filter; 17410} 17411 17412ASTInterpreter.prototype = { 17413 compile: function(expression, expensiveChecks) { 17414 var self = this; 17415 var ast = this.astBuilder.ast(expression); 17416 this.expression = expression; 17417 this.expensiveChecks = expensiveChecks; 17418 findConstantAndWatchExpressions(ast, self.$filter); 17419 var assignable; 17420 var assign; 17421 if ((assignable = assignableAST(ast))) { 17422 assign = this.recurse(assignable); 17423 } 17424 var toWatch = getInputs(ast.body); 17425 var inputs; 17426 if (toWatch) { 17427 inputs = []; 17428 forEach(toWatch, function(watch, key) { 17429 var input = self.recurse(watch); 17430 watch.input = input; 17431 inputs.push(input); 17432 watch.watchId = key; 17433 }); 17434 } 17435 var expressions = []; 17436 forEach(ast.body, function(expression) { 17437 expressions.push(self.recurse(expression.expression)); 17438 }); 17439 var fn = ast.body.length === 0 ? noop : 17440 ast.body.length === 1 ? expressions[0] : 17441 function(scope, locals) { 17442 var lastValue; 17443 forEach(expressions, function(exp) { 17444 lastValue = exp(scope, locals); 17445 }); 17446 return lastValue; 17447 }; 17448 if (assign) { 17449 fn.assign = function(scope, value, locals) { 17450 return assign(scope, locals, value); 17451 }; 17452 } 17453 if (inputs) { 17454 fn.inputs = inputs; 17455 } 17456 fn.literal = isLiteral(ast); 17457 fn.constant = isConstant(ast); 17458 return fn; 17459 }, 17460 17461 recurse: function(ast, context, create) { 17462 var left, right, self = this, args, expression; 17463 if (ast.input) { 17464 return this.inputs(ast.input, ast.watchId); 17465 } 17466 switch (ast.type) { 17467 case AST.Literal: 17468 return this.value(ast.value, context); 17469 case AST.UnaryExpression: 17470 right = this.recurse(ast.argument); 17471 return this['unary' + ast.operator](right, context); 17472 case AST.BinaryExpression: 17473 left = this.recurse(ast.left); 17474 right = this.recurse(ast.right); 17475 return this['binary' + ast.operator](left, right, context); 17476 case AST.LogicalExpression: 17477 left = this.recurse(ast.left); 17478 right = this.recurse(ast.right); 17479 return this['binary' + ast.operator](left, right, context); 17480 case AST.ConditionalExpression: 17481 return this['ternary?:']( 17482 this.recurse(ast.test), 17483 this.recurse(ast.alternate), 17484 this.recurse(ast.consequent), 17485 context 17486 ); 17487 case AST.Identifier: 17488 ensureSafeMemberName(ast.name, self.expression); 17489 return self.identifier(ast.name, 17490 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 17491 context, create, self.expression); 17492 case AST.MemberExpression: 17493 left = this.recurse(ast.object, false, !!create); 17494 if (!ast.computed) { 17495 ensureSafeMemberName(ast.property.name, self.expression); 17496 right = ast.property.name; 17497 } 17498 if (ast.computed) right = this.recurse(ast.property); 17499 return ast.computed ? 17500 this.computedMember(left, right, context, create, self.expression) : 17501 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 17502 case AST.CallExpression: 17503 args = []; 17504 forEach(ast.arguments, function(expr) { 17505 args.push(self.recurse(expr)); 17506 }); 17507 if (ast.filter) right = this.$filter(ast.callee.name); 17508 if (!ast.filter) right = this.recurse(ast.callee, true); 17509 return ast.filter ? 17510 function(scope, locals, assign, inputs) { 17511 var values = []; 17512 for (var i = 0; i < args.length; ++i) { 17513 values.push(args[i](scope, locals, assign, inputs)); 17514 } 17515 var value = right.apply(undefined, values, inputs); 17516 return context ? {context: undefined, name: undefined, value: value} : value; 17517 } : 17518 function(scope, locals, assign, inputs) { 17519 var rhs = right(scope, locals, assign, inputs); 17520 var value; 17521 if (rhs.value != null) { 17522 ensureSafeObject(rhs.context, self.expression); 17523 ensureSafeFunction(rhs.value, self.expression); 17524 var values = []; 17525 for (var i = 0; i < args.length; ++i) { 17526 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 17527 } 17528 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 17529 } 17530 return context ? {value: value} : value; 17531 }; 17532 case AST.AssignmentExpression: 17533 left = this.recurse(ast.left, true, 1); 17534 right = this.recurse(ast.right); 17535 return function(scope, locals, assign, inputs) { 17536 var lhs = left(scope, locals, assign, inputs); 17537 var rhs = right(scope, locals, assign, inputs); 17538 ensureSafeObject(lhs.value, self.expression); 17539 ensureSafeAssignContext(lhs.context); 17540 lhs.context[lhs.name] = rhs; 17541 return context ? {value: rhs} : rhs; 17542 }; 17543 case AST.ArrayExpression: 17544 args = []; 17545 forEach(ast.elements, function(expr) { 17546 args.push(self.recurse(expr)); 17547 }); 17548 return function(scope, locals, assign, inputs) { 17549 var value = []; 17550 for (var i = 0; i < args.length; ++i) { 17551 value.push(args[i](scope, locals, assign, inputs)); 17552 } 17553 return context ? {value: value} : value; 17554 }; 17555 case AST.ObjectExpression: 17556 args = []; 17557 forEach(ast.properties, function(property) { 17558 args.push({key: property.key.type === AST.Identifier ? 17559 property.key.name : 17560 ('' + property.key.value), 17561 value: self.recurse(property.value) 17562 }); 17563 }); 17564 return function(scope, locals, assign, inputs) { 17565 var value = {}; 17566 for (var i = 0; i < args.length; ++i) { 17567 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 17568 } 17569 return context ? {value: value} : value; 17570 }; 17571 case AST.ThisExpression: 17572 return function(scope) { 17573 return context ? {value: scope} : scope; 17574 }; 17575 case AST.LocalsExpression: 17576 return function(scope, locals) { 17577 return context ? {value: locals} : locals; 17578 }; 17579 case AST.NGValueParameter: 17580 return function(scope, locals, assign) { 17581 return context ? {value: assign} : assign; 17582 }; 17583 } 17584 }, 17585 17586 'unary+': function(argument, context) { 17587 return function(scope, locals, assign, inputs) { 17588 var arg = argument(scope, locals, assign, inputs); 17589 if (isDefined(arg)) { 17590 arg = +arg; 17591 } else { 17592 arg = 0; 17593 } 17594 return context ? {value: arg} : arg; 17595 }; 17596 }, 17597 'unary-': function(argument, context) { 17598 return function(scope, locals, assign, inputs) { 17599 var arg = argument(scope, locals, assign, inputs); 17600 if (isDefined(arg)) { 17601 arg = -arg; 17602 } else { 17603 arg = 0; 17604 } 17605 return context ? {value: arg} : arg; 17606 }; 17607 }, 17608 'unary!': function(argument, context) { 17609 return function(scope, locals, assign, inputs) { 17610 var arg = !argument(scope, locals, assign, inputs); 17611 return context ? {value: arg} : arg; 17612 }; 17613 }, 17614 'binary+': function(left, right, context) { 17615 return function(scope, locals, assign, inputs) { 17616 var lhs = left(scope, locals, assign, inputs); 17617 var rhs = right(scope, locals, assign, inputs); 17618 var arg = plusFn(lhs, rhs); 17619 return context ? {value: arg} : arg; 17620 }; 17621 }, 17622 'binary-': function(left, right, context) { 17623 return function(scope, locals, assign, inputs) { 17624 var lhs = left(scope, locals, assign, inputs); 17625 var rhs = right(scope, locals, assign, inputs); 17626 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 17627 return context ? {value: arg} : arg; 17628 }; 17629 }, 17630 'binary*': function(left, right, context) { 17631 return function(scope, locals, assign, inputs) { 17632 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 17633 return context ? {value: arg} : arg; 17634 }; 17635 }, 17636 'binary/': function(left, right, context) { 17637 return function(scope, locals, assign, inputs) { 17638 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 17639 return context ? {value: arg} : arg; 17640 }; 17641 }, 17642 'binary%': function(left, right, context) { 17643 return function(scope, locals, assign, inputs) { 17644 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 17645 return context ? {value: arg} : arg; 17646 }; 17647 }, 17648 'binary===': function(left, right, context) { 17649 return function(scope, locals, assign, inputs) { 17650 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 17651 return context ? {value: arg} : arg; 17652 }; 17653 }, 17654 'binary!==': function(left, right, context) { 17655 return function(scope, locals, assign, inputs) { 17656 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 17657 return context ? {value: arg} : arg; 17658 }; 17659 }, 17660 'binary==': function(left, right, context) { 17661 return function(scope, locals, assign, inputs) { 17662 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 17663 return context ? {value: arg} : arg; 17664 }; 17665 }, 17666 'binary!=': function(left, right, context) { 17667 return function(scope, locals, assign, inputs) { 17668 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 17669 return context ? {value: arg} : arg; 17670 }; 17671 }, 17672 'binary<': function(left, right, context) { 17673 return function(scope, locals, assign, inputs) { 17674 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 17675 return context ? {value: arg} : arg; 17676 }; 17677 }, 17678 'binary>': function(left, right, context) { 17679 return function(scope, locals, assign, inputs) { 17680 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 17681 return context ? {value: arg} : arg; 17682 }; 17683 }, 17684 'binary<=': function(left, right, context) { 17685 return function(scope, locals, assign, inputs) { 17686 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 17687 return context ? {value: arg} : arg; 17688 }; 17689 }, 17690 'binary>=': function(left, right, context) { 17691 return function(scope, locals, assign, inputs) { 17692 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 17693 return context ? {value: arg} : arg; 17694 }; 17695 }, 17696 'binary&&': function(left, right, context) { 17697 return function(scope, locals, assign, inputs) { 17698 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 17699 return context ? {value: arg} : arg; 17700 }; 17701 }, 17702 'binary||': function(left, right, context) { 17703 return function(scope, locals, assign, inputs) { 17704 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 17705 return context ? {value: arg} : arg; 17706 }; 17707 }, 17708 'ternary?:': function(test, alternate, consequent, context) { 17709 return function(scope, locals, assign, inputs) { 17710 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 17711 return context ? {value: arg} : arg; 17712 }; 17713 }, 17714 value: function(value, context) { 17715 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 17716 }, 17717 identifier: function(name, expensiveChecks, context, create, expression) { 17718 return function(scope, locals, assign, inputs) { 17719 var base = locals && (name in locals) ? locals : scope; 17720 if (create && create !== 1 && base && !(base[name])) { 17721 base[name] = {}; 17722 } 17723 var value = base ? base[name] : undefined; 17724 if (expensiveChecks) { 17725 ensureSafeObject(value, expression); 17726 } 17727 if (context) { 17728 return {context: base, name: name, value: value}; 17729 } else { 17730 return value; 17731 } 17732 }; 17733 }, 17734 computedMember: function(left, right, context, create, expression) { 17735 return function(scope, locals, assign, inputs) { 17736 var lhs = left(scope, locals, assign, inputs); 17737 var rhs; 17738 var value; 17739 if (lhs != null) { 17740 rhs = right(scope, locals, assign, inputs); 17741 rhs = getStringValue(rhs); 17742 ensureSafeMemberName(rhs, expression); 17743 if (create && create !== 1) { 17744 ensureSafeAssignContext(lhs); 17745 if (lhs && !(lhs[rhs])) { 17746 lhs[rhs] = {}; 17747 } 17748 } 17749 value = lhs[rhs]; 17750 ensureSafeObject(value, expression); 17751 } 17752 if (context) { 17753 return {context: lhs, name: rhs, value: value}; 17754 } else { 17755 return value; 17756 } 17757 }; 17758 }, 17759 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 17760 return function(scope, locals, assign, inputs) { 17761 var lhs = left(scope, locals, assign, inputs); 17762 if (create && create !== 1) { 17763 ensureSafeAssignContext(lhs); 17764 if (lhs && !(lhs[right])) { 17765 lhs[right] = {}; 17766 } 17767 } 17768 var value = lhs != null ? lhs[right] : undefined; 17769 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 17770 ensureSafeObject(value, expression); 17771 } 17772 if (context) { 17773 return {context: lhs, name: right, value: value}; 17774 } else { 17775 return value; 17776 } 17777 }; 17778 }, 17779 inputs: function(input, watchId) { 17780 return function(scope, value, locals, inputs) {
17781 if (inputs) return inputs[watchId]; 17782 return input(scope, value, locals); 17783 }; 17784 } 17785}; 17786 17787/** 17788 * @constructor 17789 */ 17790var Parser = function(lexer, $filter, options) { 17791 this.lexer = lexer; 17792 this.$filter = $filter; 17793 this.options = options; 17794 this.ast = new AST(lexer, options); 17795 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 17796 new ASTCompiler(this.ast, $filter); 17797}; 17798 17799Parser.prototype = { 17800 constructor: Parser, 17801 17802 parse: function(text) { 17803 return this.astCompiler.compile(text, this.options.expensiveChecks); 17804 } 17805}; 17806 17807function isPossiblyDangerousMemberName(name) { 17808 return name == 'constructor'; 17809} 17810 17811var objectValueOf = Object.prototype.valueOf; 17812 17813function getValueOf(value) { 17814 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 17815} 17816 17817/////////////////////////////////// 17818 17819/** 17820 * @ngdoc service 17821 * @name $parse 17822 * @kind function 17823 * 17824 * @description 17825 * 17826 * Converts Angular {@link guide/expression expression} into a function. 17827 * 17828 * ```js 17829 * var getter = $parse('user.name'); 17830 * var setter = getter.assign; 17831 * var context = {user:{name:'angular'}}; 17832 * var locals = {user:{name:'local'}}; 17833 * 17834 * expect(getter(context)).toEqual('angular'); 17835 * setter(context, 'newValue'); 17836 * expect(context.user.name).toEqual('newValue'); 17837 * expect(getter(context, locals)).toEqual('local'); 17838 * ``` 17839 * 17840 * 17841 * @param {string} expression String expression to compile. 17842 * @returns {function(context, locals)} a function which represents the compiled expression: 17843 * 17844 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17845 * are evaluated against (typically a scope object). 17846 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17847 * `context`. 17848 * 17849 * The returned function also has the following properties: 17850 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 17851 * literal. 17852 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 17853 * constant literals. 17854 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 17855 * set to a function to change its value on the given context. 17856 * 17857 */ 17858 17859 17860/** 17861 * @ngdoc provider 17862 * @name $parseProvider 17863 * 17864 * @description 17865 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 17866 * service. 17867 */ 17868function $ParseProvider() { 17869 var cacheDefault = createMap(); 17870 var cacheExpensive = createMap(); 17871 var literals = { 17872 'true': true, 17873 'false': false, 17874 'null': null, 17875 'undefined': undefined 17876 }; 17877 var identStart, identContinue; 17878 17879 /** 17880 * @ngdoc method 17881 * @name $parseProvider#addLiteral 17882 * @description 17883 * 17884 * Configure $parse service to add literal values that will be present as literal at expressions. 17885 * 17886 * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. 17887 * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. 17888 * 17889 **/ 17890 this.addLiteral = function(literalName, literalValue) { 17891 literals[literalName] = literalValue; 17892 }; 17893 17894 /** 17895 * @ngdoc method 17896 * @name $parseProvider#setIdentifierFns 17897 * @description 17898 * 17899 * Allows defining the set of characters that are allowed in Angular expressions. The function 17900 * `identifierStart` will get called to know if a given character is a valid character to be the 17901 * first character for an identifier. The function `identifierContinue` will get called to know if 17902 * a given character is a valid character to be a follow-up identifier character. The functions 17903 * `identifierStart` and `identifierContinue` will receive as arguments the single character to be 17904 * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in 17905 * mind that the `string` parameter can be two characters long depending on the character 17906 * representation. It is expected for the function to return `true` or `false`, whether that 17907 * character is allowed or not. 17908 * 17909 * Since this function will be called extensivelly, keep the implementation of these functions fast, 17910 * as the performance of these functions have a direct impact on the expressions parsing speed. 17911 * 17912 * @param {function=} identifierStart The function that will decide whether the given character is 17913 * a valid identifier start character. 17914 * @param {function=} identifierContinue The function that will decide whether the given character is 17915 * a valid identifier continue character. 17916 */ 17917 this.setIdentifierFns = function(identifierStart, identifierContinue) { 17918 identStart = identifierStart; 17919 identContinue = identifierContinue; 17920 return this; 17921 }; 17922 17923 this.$get = ['$filter', function($filter) { 17924 var noUnsafeEval = csp().noUnsafeEval; 17925 var $parseOptions = { 17926 csp: noUnsafeEval,
17927 expensiveChecks: false, 17928 literals: copy(literals), 17929 isIdentifierStart: isFunction(identStart) && identStart, 17930 isIdentifierContinue: isFunction(identContinue) && identContinue 17931 }, 17932 $parseOptionsExpensive = { 17933 csp: noUnsafeEval, 17934 expensiveChecks: true, 17935 literals: copy(literals), 17936 isIdentifierStart: isFunction(identStart) && identStart, 17937 isIdentifierContinue: isFunction(identContinue) && identContinue 17938 }; 17939 var runningChecksEnabled = false; 17940 17941 $parse.$$runningExpensiveChecks = function() { 17942 return runningChecksEnabled; 17943 }; 17944 17945 return $parse; 17946 17947 function $parse(exp, interceptorFn, expensiveChecks) { 17948 var parsedExpression, oneTime, cacheKey; 17949 17950 expensiveChecks = expensiveChecks || runningChecksEnabled; 17951 17952 switch (typeof exp) { 17953 case 'string': 17954 exp = exp.trim(); 17955 cacheKey = exp; 17956 17957 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 17958 parsedExpression = cache[cacheKey]; 17959 17960 if (!parsedExpression) { 17961 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 17962 oneTime = true; 17963 exp = exp.substring(2); 17964 } 17965 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 17966 var lexer = new Lexer(parseOptions); 17967 var parser = new Parser(lexer, $filter, parseOptions); 17968 parsedExpression = parser.parse(exp); 17969 if (parsedExpression.constant) { 17970 parsedExpression.$$watchDelegate = constantWatchDelegate; 17971 } else if (oneTime) { 17972 parsedExpression.$$watchDelegate = parsedExpression.literal ? 17973 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 17974 } else if (parsedExpression.inputs) { 17975 parsedExpression.$$watchDelegate = inputsWatchDelegate; 17976 } 17977 if (expensiveChecks) { 17978 parsedExpression = expensiveChecksInterceptor(parsedExpression); 17979 } 17980 cache[cacheKey] = parsedExpression; 17981 } 17982 return addInterceptor(parsedExpression, interceptorFn); 17983 17984 case 'function': 17985 return addInterceptor(exp, interceptorFn); 17986 17987 default: 17988 return addInterceptor(noop, interceptorFn); 17989 } 17990 } 17991 17992 function expensiveChecksInterceptor(fn) { 17993 if (!fn) return fn; 17994 expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate; 17995 expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign); 17996 expensiveCheckFn.constant = fn.constant; 17997 expensiveCheckFn.literal = fn.literal; 17998 for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) { 17999 fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]); 18000 } 18001 expensiveCheckFn.inputs = fn.inputs; 18002 18003 return expensiveCheckFn; 18004 18005 function expensiveCheckFn(scope, locals, assign, inputs) { 18006 var expensiveCheckOldValue = runningChecksEnabled; 18007 runningChecksEnabled = true; 18008 try { 18009 return fn(scope, locals, assign, inputs); 18010 } finally { 18011 runningChecksEnabled = expensiveCheckOldValue; 18012 } 18013 } 18014 } 18015 18016 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 18017 18018 if (newValue == null || oldValueOfValue == null) { // null/undefined 18019 return newValue === oldValueOfValue; 18020 } 18021 18022 if (typeof newValue === 'object') { 18023 18024 // attempt to convert the value to a primitive type 18025 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 18026 // be cheaply dirty-checked 18027 newValue = getValueOf(newValue); 18028 18029 if (typeof newValue === 'object') { 18030 // objects/arrays are not supported - deep-watching them would be too expensive 18031 return false; 18032 } 18033 18034 // fall-through to the primitive equality check 18035 } 18036 18037 //Primitive or NaN 18038 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 18039 } 18040 18041 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 18042 var inputExpressions = parsedExpression.inputs; 18043 var lastResult; 18044 18045 if (inputExpressions.length === 1) { 18046 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 18047 inputExpressions = inputExpressions[0]; 18048 return scope.$watch(function expressionInputWatch(scope) { 18049 var newInputValue = inputExpressions(scope); 18050 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 18051 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 18052 oldInputValueOf = newInputValue && getValueOf(newInputValue); 18053 } 18054 return lastResult; 18055 }, listener, objectEquality, prettyPrintExpression); 18056 } 18057 18058 var oldInputValueOfValues = []; 18059 var oldInputValues = []; 18060 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 18061 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 18062 oldInputValues[i] = null; 18063 } 18064 18065 return scope.$watch(function expressionInputsWatch(scope) { 18066 var changed = false;
18067 18068 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 18069 var newInputValue = inputExpressions[i](scope); 18070 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 18071 oldInputValues[i] = newInputValue; 18072 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 18073 } 18074 } 18075 18076 if (changed) { 18077 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 18078 } 18079 18080 return lastResult; 18081 }, listener, objectEquality, prettyPrintExpression); 18082 } 18083 18084 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 18085 var unwatch, lastValue; 18086 return unwatch = scope.$watch(function oneTimeWatch(scope) { 18087 return parsedExpression(scope); 18088 }, function oneTimeListener(value, old, scope) { 18089 lastValue = value; 18090 if (isFunction(listener)) { 18091 listener.apply(this, arguments); 18092 } 18093 if (isDefined(value)) { 18094 scope.$$postDigest(function() { 18095 if (isDefined(lastValue)) { 18096 unwatch(); 18097 } 18098 }); 18099 } 18100 }, objectEquality); 18101 } 18102 18103 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 18104 var unwatch, lastValue; 18105 return unwatch = scope.$watch(function oneTimeWatch(scope) { 18106 return parsedExpression(scope); 18107 }, function oneTimeListener(value, old, scope) { 18108 lastValue = value; 18109 if (isFunction(listener)) { 18110 listener.call(this, value, old, scope); 18111 } 18112 if (isAllDefined(value)) { 18113 scope.$$postDigest(function() { 18114 if (isAllDefined(lastValue)) unwatch(); 18115 }); 18116 } 18117 }, objectEquality); 18118 18119 function isAllDefined(value) { 18120 var allDefined = true; 18121 forEach(value, function(val) { 18122 if (!isDefined(val)) allDefined = false; 18123 }); 18124 return allDefined; 18125 } 18126 } 18127 18128 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 18129 var unwatch; 18130 return unwatch = scope.$watch(function constantWatch(scope) { 18131 unwatch(); 18132 return parsedExpression(scope); 18133 }, listener, objectEquality); 18134 } 18135 18136 function addInterceptor(parsedExpression, interceptorFn) { 18137 if (!interceptorFn) return parsedExpression; 18138 var watchDelegate = parsedExpression.$$watchDelegate; 18139 var useInputs = false; 18140 18141 var regularWatch = 18142 watchDelegate !== oneTimeLiteralWatchDelegate && 18143 watchDelegate !== oneTimeWatchDelegate; 18144 18145 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { 18146 var value = useInputs && inputs ? inputs[0] : parsedExpression(scope, locals, assign, inputs); 18147 return interceptorFn(value, scope, locals); 18148 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 18149 var value = parsedExpression(scope, locals, assign, inputs); 18150 var result = interceptorFn(value, scope, locals); 18151 // we only return the interceptor's result if the 18152 // initial value is defined (for bind-once) 18153 return isDefined(value) ? result : value; 18154 }; 18155 18156 // Propagate $$watchDelegates other then inputsWatchDelegate 18157 if (parsedExpression.$$watchDelegate && 18158 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 18159 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 18160 } else if (!interceptorFn.$stateful) { 18161 // If there is an interceptor, but no watchDelegate then treat the interceptor like 18162 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 18163 fn.$$watchDelegate = inputsWatchDelegate; 18164 useInputs = !parsedExpression.inputs; 18165 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 18166 } 18167 18168 return fn; 18169 } 18170 }]; 18171} 18172 18173/** 18174 * @ngdoc service 18175 * @name $q 18176 * @requires $rootScope 18177 * 18178 * @description 18179 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 18180 * when they are done processing. 18181 * 18182 * This is an implementation of promises/deferred objects inspired by 18183 * [Kris Kowal's Q](https://github.com/kriskowal/q). 18184 * 18185 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 18186 * implementations, and the other which resembles ES6 (ES2015) promises to
18186some degree. 18187 * 18188 * # $q constructor 18189 * 18190 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 18191 * function as the first argument. This is similar to the native Promise implementation from ES6, 18192 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 18193 * 18194 * While the constructor-style use is supported, not all of the supporting methods from ES6 promises are 18195 * available yet. 18196 * 18197 * It can be used like so: 18198 * 18199 * ```js 18200 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 18201 * // are available in the current lexical scope (they could have been injected or passed in). 18202 * 18203 * function asyncGreet(name) { 18204 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 18205 * return $q(function(resolve, reject) { 18206 * setTimeout(function() { 18207 * if (okToGreet(name)) { 18208 * resolve('Hello, ' + name + '!'); 18209 * } else { 18210 * reject('Greeting ' + name + ' is not allowed.'); 18211 * } 18212 * }, 1000); 18213 * }); 18214 * } 18215 * 18216 * var promise = asyncGreet('Robin Hood'); 18217 * promise.then(function(greeting) { 18218 * alert('Success: ' + greeting); 18219 * }, function(reason) { 18220 * alert('Failed: ' + reason); 18221 * }); 18222 * ``` 18223 * 18224 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 18225 * 18226 * Note: unlike ES6 behavior, an exception thrown in the constructor function will NOT implicitly reject the promise. 18227 * 18228 * However, the more traditional CommonJS-style usage is still available, and documented below. 18229 * 18230 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 18231 * interface for interacting with an object that represents the result of an action that is 18232 * performed asynchronously, and may or may not be finished at any given point in time. 18233 * 18234 * From the perspective of dealing with error handling, deferred and promise APIs are to 18235 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 18236 * 18237 * ```js 18238 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 18239 * // are available in the current lexical scope (they could have been injected or passed in). 18240 * 18241 * function asyncGreet(name) { 18242 * var deferred = $q.defer(); 18243 * 18244 * setTimeout(function() { 18245 * deferred.notify('About to greet ' + name + '.'); 18246 * 18247 * if (okToGreet(name)) { 18248 * deferred.resolve('Hello, ' + name + '!'); 18249 * } else { 18250 * deferred.reject('Greeting ' + name + ' is not allowed.'); 18251 * } 18252 * }, 1000); 18253 * 18254 * return deferred.promise; 18255 * } 18256 * 18257 * var promise = asyncGreet('Robin Hood'); 18258 * promise.then(function(greeting) { 18259 * alert('Success: ' + greeting); 18260 * }, function(reason) { 18261 * alert('Failed: ' + reason); 18262 * }, function(update) { 18263 * alert('Got notification: ' + update); 18264 * }); 18265 * ``` 18266 * 18267 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 18268 * comes in the way of guarantees that promise and deferred APIs make, see 18269 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 18270 * 18271 * Additionally the promise api allows for composition that is very hard to do with the 18272 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 18273 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 18274 * section on serial or parallel joining of promises. 18275 * 18276 * # The Deferred API 18277 * 18278 * A new instance of deferred is constructed by calling `$q.defer()`. 18279 * 18280 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 18281 * that can be used for signaling the successful or unsuccessful completion, as well as the status 18282 * of the task. 18283 * 18284 * **Methods** 18285 * 18286 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 18287 * constructed via `$q.reject`, the promise will be rejected instead. 18288 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 18289 * resolving it with a rejection constructed via `$q.reject`. 18290 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 18291 * multiple times before the promise is either resolved or rejected. 18292 * 18293 * **Properties** 18294 * 18295 * - promise â `{Promise}` â promise object associated with this deferred. 18296 * 18297 * 18298 * # The Promise API 18299 * 18300 * A new promise instance is created when a deferred instance is created and can be retrieved by 18301 * calling `deferred.promise`. 18302 * 18303 * The purpose of the promise object is to allow for interested parties to get access to the result 18304 * of the deferred task when it completes. 18305 * 18306 * **Methods** 18307 * 18308 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 18309 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 18310 * as soon as the result is available. The callbacks are called with a single argument: the result 18311 * or rejection reason. Additionally, the notify callback may be called zero or more times to 18312 * provide a progress indication, before the promise is resolved or rejected. 18313 * 18314 * This method *returns a new promise* which is resolved or rejected via the return value of the 18315 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
18316 * with the value which is resolved in that promise using 18317 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 18318 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 18319 * resolved or rejected from the notifyCallback method. 18320 * 18321 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 18322 * 18323 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 18324 * but to do so without modifying the final value. This is useful to release resources or do some 18325 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 18326 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 18327 * more information. 18328 * 18329 * # Chaining promises 18330 * 18331 * Because calling the `then` method of a promise returns a new derived promise, it is easily 18332 * possible to create a chain of promises: 18333 * 18334 * ```js 18335 * promiseB = promiseA.then(function(result) { 18336 * return result + 1; 18337 * }); 18338 * 18339 * // promiseB will be resolved immediately after promiseA is resolved and its value 18340 * // will be the result of promiseA incremented by 1 18341 * ``` 18342 * 18343 * It is possible to create chains of any length and since a promise can be resolved with another 18344 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 18345 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 18346 * $http's response interceptors. 18347 * 18348 * 18349 * # Differences between Kris Kowal's Q and $q 18350 * 18351 * There are two main differences: 18352 * 18353 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 18354 * mechanism in angular, which means faster propagation of resolution or rejection into your 18355 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 18356 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
18357 * all the important functionality needed for common async tasks. 18358 * 18359 * # Testing 18360 * 18361 * ```js 18362 * it('should simulate promise', inject(function($q, $rootScope) { 18363 * var deferred = $q.defer(); 18364 * var promise = deferred.promise; 18365 * var resolvedValue; 18366 * 18367 * promise.then(function(value) { resolvedValue = value; }); 18368 * expect(resolvedValue).toBeUndefined(); 18369 * 18370 * // Simulate resolving of promise 18371 * deferred.resolve(123); 18372 * // Note that the 'then' function does not get called synchronously. 18373 * // This is because we want the promise API to always be async, whether or not 18374 * // it got called synchronously or asynchronously. 18375 * expect(resolvedValue).toBeUndefined(); 18376 * 18377 * // Propagate promise resolution to 'then' functions using $apply(). 18378 * $rootScope.$apply(); 18379 * expect(resolvedValue).toEqual(123); 18380 * })); 18381 * ``` 18382 * 18383 * @param {function(function, function)} resolver Function which is responsible for resolving or 18384 * rejecting the newly created promise. The first parameter is a function which resolves the 18385 * promise, the second parameter is a function which rejects the promise. 18386 * 18387 * @returns {Promise} The newly created promise. 18388 */ 18389function $QProvider() { 18390 18391 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 18392 return qFactory(function(callback) { 18393 $rootScope.$evalAsync(callback); 18394 }, $exceptionHandler); 18395 }]; 18396} 18397 18398function $$QProvider() { 18399 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 18400 return qFactory(function(callback) { 18401 $browser.defer(callback); 18402 }, $exceptionHandler); 18403 }]; 18404} 18405 18406/** 18407 * Constructs a promise manager. 18408 * 18409 * @param {function(function)} nextTick Function for executing functions in the next turn. 18410 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 18411 * debugging purposes. 18412 * @returns {object} Promise manager. 18413 */ 18414function qFactory(nextTick, exceptionHandler) { 18415 var $qMinErr = minErr('$q', TypeError); 18416 18417 /** 18418 * @ngdoc method 18419 * @name ng.$q#defer 18420 * @kind function 18421 * 18422 * @description 18423 * Creates a `Deferred` object which represents a task which will finish in the future. 18424 * 18425 * @returns {Deferred} Returns a new instance of deferred. 18426 */ 18427 var defer = function() { 18428 var d = new Deferred(); 18429 //Necessary to support unbound execution :/ 18430 d.resolve = simpleBind(d, d.resolve); 18431 d.reject = simpleBind(d, d.reject); 18432 d.notify = simpleBind(d, d.notify); 18433 return d; 18434 }; 18435 18436 function Promise() { 18437 this.$$state = { status: 0 }; 18438 } 18439 18440 extend(Promise.prototype, { 18441 then: function(onFulfilled, onRejected, progressBack) { 18442 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 18443 return this; 18444 } 18445 var result = new Deferred(); 18446 18447 this.$$state.pending = this.$$state.pending || []; 18448 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 18449 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 18450 18451 return result.promise; 18452 }, 18453 18454 "catch": function(callback) { 18455 return this.then(null, callback); 18456 }, 18457 18458 "finally": function(callback, progressBack) { 18459 return this.then(function(value) { 18460 return handleCallback(value, true, callback); 18461 }, function(error) { 18462 return handleCallback(error, false, callback); 18463 }, progressBack); 18464 } 18465 }); 18466 18467 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 18468 function simpleBind(context, fn) { 18469 return function(value) { 18470 fn.call(context, value); 18471 }; 18472 } 18473 18474 function processQueue(state) { 18475 var fn, deferred, pending; 18476 18477 pending = state.pending; 18478 state.processScheduled = false; 18479 state.pending = undefined; 18480 for (var i = 0, ii = pending.length; i < ii; ++i) { 18481 deferred = pending[i][0]; 18482 fn = pending[i][state.status]; 18483 try { 18484 if (isFunction(fn)) { 18485 deferred.resolve(fn(state.value)); 18486 } else if (state.status === 1) { 18487 deferred.resolve(state.value); 18488 } else { 18489 deferred.reject(state.value); 18490 } 18491 } catch (e) { 18492 deferred.reject(e); 18493 exceptionHandler(e); 18494 } 18495 } 18496 } 18497 18498 function scheduleProcessQueue(state) { 18499 if (state.processScheduled || !state.pending) return; 18500 state.processScheduled = true; 18501 nextTick(function() { processQueue(state); }); 18502 } 18503 18504 function Deferred() { 18505 this.promise = new Promise(); 18506 } 18507 18508 extend(Deferred.prototype, { 18509 resolve: function(val) { 18510 if (this.promise.$$state.status) return; 18511 if (val === this.promise) { 18512 this.$$reject($qMinErr( 18513 'qcycle', 18514 "Expected promise to be resolved with value other than
18514itself '{0}'", 18515 val)); 18516 } else { 18517 this.$$resolve(val); 18518 } 18519 18520 }, 18521 18522 $$resolve: function(val) { 18523 var then; 18524 var that = this; 18525 var done = false; 18526 try { 18527 if ((isObject(val) || isFunction(val))) then = val && val.then; 18528 if (isFunction(then)) { 18529 this.promise.$$state.status = -1; 18530 then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify)); 18531 } else { 18532 this.promise.$$state.value = val; 18533 this.promise.$$state.status = 1; 18534 scheduleProcessQueue(this.promise.$$state); 18535 } 18536 } catch (e) { 18537 rejectPromise(e); 18538 exceptionHandler(e); 18539 } 18540 18541 function resolvePromise(val) { 18542 if (done) return; 18543 done = true; 18544 that.$$resolve(val); 18545 } 18546 function rejectPromise(val) { 18547 if (done) return; 18548 done = true; 18549 that.$$reject(val); 18550 } 18551 }, 18552 18553 reject: function(reason) { 18554 if (this.promise.$$state.status) return; 18555 this.$$reject(reason); 18556 }, 18557 18558 $$reject: function(reason) { 18559 this.promise.$$state.value = reason; 18560 this.promise.$$state.status = 2; 18561 scheduleProcessQueue(this.promise.$$state); 18562 }, 18563 18564 notify: function(progress) { 18565 var callbacks = this.promise.$$state.pending; 18566 18567 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 18568 nextTick(function() { 18569 var callback, result; 18570 for (var i = 0, ii = callbacks.length; i < ii; i++) { 18571 result = callbacks[i][0]; 18572 callback = callbacks[i][3]; 18573 try { 18574 result.notify(isFunction(callback) ? callback(progress) : progress); 18575 } catch (e) { 18576 exceptionHandler(e); 18577 } 18578 } 18579 }); 18580 } 18581 } 18582 }); 18583 18584 /** 18585 * @ngdoc method 18586 * @name $q#reject 18587 * @kind function 18588 * 18589 * @description 18590 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 18591 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 18592 * a promise chain, you don't need to worry about it. 18593 * 18594 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 18595 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 18596 * a promise error callback and you want to forward the error to the promise derived from the 18597 * current promise, you have to "rethrow" the error by returning a rejection constructed via 18598 * `reject`. 18599 * 18600 * ```js 18601 * promiseB = promiseA.then(function(result) { 18602 * // success: do something and resolve promiseB 18603 * // with the old or a new result 18604 * return result; 18605 * }, function(reason) { 18606 * // error: handle the error if possible and 18607 * // resolve promiseB with newPromiseOrValue, 18608 * // otherwise forward the rejection to promiseB 18609 * if (canHandle(reason)) { 18610 * // handle the error and recover 18611 * return newPromiseOrValue; 18612 * } 18613 * return $q.reject(reason); 18614 * }); 18615 * ``` 18616 * 18617 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 18618 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 18619 */ 18620 var reject = function(reason) { 18621 var result = new Deferred(); 18622 result.reject(reason); 18623 return result.promise; 18624 }; 18625 18626 var makePromise = function makePromise(value, resolved) { 18627 var result = new Deferred(); 18628 if (resolved) { 18629 result.resolve(value); 18630 } else { 18631 result.reject(value); 18632 } 18633 return result.promise; 18634 }; 18635 18636 var handleCallback = function handleCallback(value, isResolved, callback) { 18637 var callbackOutput = null; 18638 try { 18639 if (isFunction(callback)) callbackOutput = callback(); 18640 } catch (e) { 18641 return makePromise(e, false); 18642 } 18643 if (isPromiseLike(callbackOutput)) { 18644 return callbackOutput.then(function() { 18645 return makePromise(value, isResolved); 18646 }, function(error) { 18647 return makePromise(error, false); 18648 }); 18649 } else { 18650 return makePromise(value, isResolved); 18651 } 18652 }; 18653 18654 /** 18655 * @ngdoc method 18656 * @name $q#when 18657 * @kind function 18658 * 18659 * @description 18660 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 18661 * This is useful when you are dealing with an object that might or might not be a promise, or if 18662 * the promise comes from a source that can't be trusted. 18663 * 18664 * @param {*} value Value or a promise 18665 * @param {Function=} successCallback 18666 * @param {Function=} errorCallback 18667 * @param {Function=} progressCallback 18668 * @returns {Promise} Returns a promise of the passed value or promise 18669 */ 18670 18671 18672 var when = function(value, callback, errback, progressBack) { 18673 var result = new Deferred(); 18674 result.resolve(value); 18675 return result.promise.then(callback, errback, progressBack); 18676 }; 18677 18678 /** 18679 * @ngdoc method 18680 * @name $q#resolve 18681 * @kind function 18682 * 18683 * @description 18684 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 18685 * 18686 * @param {*} value Value or a promise 18687 * @param {Function=} successCallback 18688 * @param {Function=} errorCallback 18689 * @param {Function=} progressCallback 18690 * @returns {Promise} Returns a promise of the passed value or promise 18691 */ 18692 var resolve = when; 18693 18694 /** 18695 * @ngdoc method 18696 * @name $q#all 18697 * @kind function 18698 * 18699 * @description 18700 * Combines multiple promises into a single promise that is resolved when all of the input 18701 * promises are resolved. 18702 * 18703 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 18704 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 18705 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 18706 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 18707 * with the same rejection value. 18708 */ 18709 18710 function all(promises) { 18711 var deferred = new Deferred(), 18712 counter = 0, 18713 results = isArray(promises) ? [] : {}; 18714
18715 forEach(promises, function(promise, key) { 18716 counter++; 18717 when(promise).then(function(value) { 18718 if (results.hasOwnProperty(key)) return; 18719 results[key] = value; 18720 if (!(--counter)) deferred.resolve(results); 18721 }, function(reason) { 18722 if (results.hasOwnProperty(key)) return; 18723 deferred.reject(reason); 18724 }); 18725 }); 18726 18727 if (counter === 0) { 18728 deferred.resolve(results); 18729 } 18730 18731 return deferred.promise; 18732 } 18733 18734 var $Q = function Q(resolver) { 18735 if (!isFunction(resolver)) { 18736 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 18737 } 18738 18739 var deferred = new Deferred(); 18740 18741 function resolveFn(value) { 18742 deferred.resolve(value); 18743 } 18744 18745 function rejectFn(reason) { 18746 deferred.reject(reason); 18747 } 18748 18749 resolver(resolveFn, rejectFn); 18750 18751 return deferred.promise; 18752 }; 18753 18754 // Let's make the instanceof operator work for promises, so that 18755 // `new $q(fn) instanceof $q` would evaluate to true. 18756 $Q.prototype = Promise.prototype; 18757 18758 $Q.defer = defer; 18759 $Q.reject = reject; 18760 $Q.when = when; 18761 $Q.resolve = resolve; 18762 $Q.all = all; 18763 18764 return $Q; 18765} 18766 18767function $$RAFProvider() { //rAF 18768 this.$get = ['$window', '$timeout', function($window, $timeout) { 18769 var requestAnimationFrame = $window.requestAnimationFrame || 18770 $window.webkitRequestAnimationFrame; 18771 18772 var cancelAnimationFrame = $window.cancelAnimationFrame || 18773 $window.webkitCancelAnimationFrame || 18774 $window.webkitCancelRequestAnimationFrame; 18775 18776 var rafSupported = !!requestAnimationFrame; 18777 var raf = rafSupported 18778 ? function(fn) { 18779 var id = requestAnimationFrame(fn); 18780 return function() { 18781 cancelAnimationFrame(id); 18782 }; 18783 } 18784 : function(fn) { 18785 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 18786 return function() { 18787 $timeout.cancel(timer); 18788 }; 18789 }; 18790 18791 raf.supported = rafSupported; 18792 18793 return raf; 18794 }]; 18795} 18796 18797/** 18798 * DESIGN NOTES 18799 * 18800 * The design decisions behind the scope are heavily favored for speed and memory consumption. 18801 * 18802 * The typical use of scope is to watch the expressions, which most of the time return the same 18803 * value as last time so we optimize the operation. 18804 * 18805 * Closures construction is expensive in terms of speed as well as memory: 18806 * - No closures, instead use prototypical inheritance for API 18807 * - Internal state needs to be stored on scope directly, which means that private state is 18808 * exposed as $$____ properties 18809 * 18810 * Loop operations are optimized by using while(count--) { ... } 18811 * - This means that in order to keep the same order of execution as addition we have to add 18812 * items to the array at the beginning (unshift) instead of at the end (push) 18813 * 18814 * Child scopes are created and removed often 18815 * - Using an array would be slow since inserts in the middle are expensive; so we use linked lists 18816 * 18817 * There are fewer watches than observers. This is why you don't want the observer to be implemented 18818 * in the same way as watch. Watch requires return of the initialization function which is expensive 18819 * to construct. 18820 */ 18821 18822 18823/** 18824 * @ngdoc provider 18825 * @name $rootScopeProvider 18826 * @description 18827 * 18828 * Provider for the $rootScope service. 18829 */ 18830 18831/** 18832 * @ngdoc method 18833 * @name $rootScopeProvider#digestTtl 18834 * @description 18835 * 18836 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 18837 * assuming that the model is unstable. 18838 * 18839 * The current default is 10 iterations. 18840 * 18841 * In complex applications it's possible that the dependencies between `$watch`s will result in 18842 * several digest iterations. However if an application needs more than the default 10 digest 18843 * iterations for its model to stabilize then you should investigate what is causing the model to 18844 * continuously change during the digest. 18845 * 18846 * Increasing the TTL could have performance implications, so you should not change it without 18847 * proper justification. 18848 * 18849 * @param {number} limit The number of digest iterations. 18850 */ 18851 18852 18853/** 18854 * @ngdoc service 18855 * @name $rootScope 18856 * @description 18857 * 18858 * Every application has a single root {@link ng.$rootScope.Scope scope}. 18859 * All other scopes are descendant scopes of the root scope. Scopes provide separation 18860 * between the model and the view, via a mechanism for watching the model for changes. 18861 * They also provide event emission/broadcast and subscription facility. See the 18862 * {@link guide/scope developer guide on scopes}. 18863 */ 18864function $RootScopeProvider() { 18865 var TTL = 10; 18866 var $rootScopeMinErr = minErr('$rootScope'); 18867 var lastDirtyWatch = null; 18868 var applyAsyncId = null; 18869 18870 this.digestTtl = function(value) { 18871 if (arguments.length) { 18872 TTL = value; 18873 } 18874 return TTL; 18875 }; 18876 18877 function createChildScopeClass(parent) { 18878 function ChildScope() {
18879 this.$$watchers = this.$$nextSibling = 18880 this.$$childHead = this.$$childTail = null; 18881 this.$$listeners = {}; 18882 this.$$listenerCount = {}; 18883 this.$$watchersCount = 0; 18884 this.$id = nextUid(); 18885 this.$$ChildScope = null; 18886 } 18887 ChildScope.prototype = parent; 18888 return ChildScope; 18889 } 18890 18891 this.$get = ['$exceptionHandler', '$parse', '$browser', 18892 function($exceptionHandler, $parse, $browser) { 18893 18894 function destroyChildScope($event) { 18895 $event.currentScope.$$destroyed = true; 18896 } 18897 18898 function cleanUpScope($scope) { 18899 18900 if (msie === 9) { 18901 // There is a memory leak in IE9 if all child scopes are not disconnected 18902 // completely when a scope is destroyed. So this code will recurse up through 18903 // all this scopes children 18904 // 18905 // See issue https://github.com/angular/angular.js/issues/10706 18906 $scope.$$childHead && cleanUpScope($scope.$$childHead); 18907 $scope.$$nextSibling && cleanUpScope($scope.$$nextSibling); 18908 } 18909 18910 // The code below works around IE9 and V8's memory leaks 18911 // 18912 // See: 18913 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 18914 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 18915 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 18916 18917 $scope.$parent = $scope.$$nextSibling = $scope.$$prevSibling = $scope.$$childHead = 18918 $scope.$$childTail = $scope.$root = $scope.$$watchers = null; 18919 } 18920 18921 /** 18922 * @ngdoc type 18923 * @name $rootScope.Scope 18924 * 18925 * @description 18926 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 18927 * {@link auto.$injector $injector}. Child scopes are created using the 18928 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 18929 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 18930 * an in-depth introduction and usage examples. 18931 * 18932 * 18933 * # Inheritance 18934 * A scope can inherit from a parent scope, as in this example: 18935 * ```js 18936 var parent = $rootScope; 18937 var child = parent.$new(); 18938 18939 parent.salutation = "Hello"; 18940 expect(child.salutation).toEqual('Hello'); 18941 18942 child.salutation = "Welcome"; 18943 expect(child.salutation).toEqual('Welcome'); 18944 expect(parent.salutation).toEqual('Hello'); 18945 * ``` 18946 * 18947 * When interacting with `Scope` in tests, additional helper methods are available on the 18948 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 18949 * details. 18950 * 18951 * 18952 * @param {Object.<string, function()>=} providers Map of service factory which need to be 18953 * provided for the current scope. Defaults to {@link ng}. 18954 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 18955 * append/override services provided by `providers`. This is handy 18956 * when unit-testing and having the need to override a default 18957 * service. 18958 * @returns {Object} Newly created scope. 18959 * 18960 */ 18961 function Scope() { 18962 this.$id = nextUid(); 18963 this.$$phase = this.$parent = this.$$watchers = 18964 this.$$nextSibling = this.$$prevSibling = 18965 this.$$childHead = this.$$childTail = null; 18966 this.$root = this; 18967 this.$$destroyed = false; 18968 this.$$listeners = {}; 18969 this.$$listenerCount = {}; 18970 this.$$watchersCount = 0; 18971 this.$$isolateBindings = null; 18972 } 18973 18974 /** 18975 * @ngdoc property 18976 * @name $rootScope.Scope#$id 18977 * 18978 * @description 18979 * Unique scope ID (monotonically increasing) useful for debugging. 18980 */ 18981 18982 /** 18983 * @ngdoc property 18984 * @name $rootScope.Scope#$parent 18985 * 18986 * @description 18987 * Reference to the parent scope. 18988 */ 18989 18990 /** 18991 * @ngdoc property 18992 * @name $rootScope.Scope#$root 18993 *
18994 * @description 18995 * Reference to the root scope. 18996 */ 18997 18998 Scope.prototype = { 18999 constructor: Scope, 19000 /** 19001 * @ngdoc method 19002 * @name $rootScope.Scope#$new 19003 * @kind function 19004 * 19005 * @description 19006 * Creates a new child {@link ng.$rootScope.Scope scope}. 19007 * 19008 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 19009 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 19010 * 19011 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 19012 * desired for the scope and its child scopes to be permanently detached from the parent and 19013 * thus stop participating in model change detection and listener notification by invoking. 19014 * 19015 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 19016 * parent scope. The scope is isolated, as it can not see parent scope properties. 19017 * When creating widgets, it is useful for the widget to not accidentally read parent 19018 * state. 19019 * 19020 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 19021 * of the newly created scope. Defaults to `this` scope if not provided. 19022 * This is used when creating a transclude scope to correctly place it 19023 * in the scope hierarchy while maintaining the correct prototypical 19024 * inheritance. 19025 * 19026 * @returns {Object} The newly created child scope. 19027 * 19028 */ 19029 $new: function(isolate, parent) { 19030 var child; 19031 19032 parent = parent || this; 19033 19034 if (isolate) { 19035 child = new Scope(); 19036 child.$root = this.$root; 19037 } else { 19038 // Only create a child scope class if somebody asks for one, 19039 // but cache it to allow the VM to optimize lookups. 19040 if (!this.$$ChildScope) { 19041 this.$$ChildScope = createChildScopeClass(this); 19042 } 19043 child = new this.$$ChildScope(); 19044 } 19045 child.$parent = parent; 19046 child.$$prevSibling = parent.$$childTail; 19047 if (parent.$$childHead) { 19048 parent.$$childTail.$$nextSibling = child; 19049 parent.$$childTail = child; 19050 } else { 19051 parent.$$childHead = parent.$$childTail = child; 19052 } 19053 19054 // When the new scope is not isolated or we inherit from `this`, and 19055 // the parent scope is destroyed, the property `$$destroyed` is inherited 19056 // prototypically. In all other cases, this property needs to be set 19057 // when the parent scope is destroyed. 19058 // The listener needs to be added after the parent is set 19059 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 19060 19061 return child; 19062 }, 19063 19064 /** 19065 * @ngdoc method 19066 * @name $rootScope.Scope#$watch 19067 * @kind function 19068 * 19069 * @description 19070 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 19071 * 19072 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 19073 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 19074 * its value when executed multiple times with the same input because it may be executed multiple 19075 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 19076 * [idempotent](http://en.wikipedia.org/wiki/Idempotence). 19077 * - The `listener` is called only when the value from the current `watchExpression` and the 19078 * previous call to `watchExpression` are not equal (with the exception of the initial run, 19079 * see below). Inequality is determined according to reference inequality, 19080 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 19081 * via the `!==` Javascript operator, unless `objectEquality == true` 19082 * (see next point) 19083 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 19084 * according to the {@link angular.equals} function. To save the value of the object for 19085 * later comparison, the {@link angular.copy} function is used. This therefore means that 19086 * watching complex objects will have adverse memory and performance implications. 19087 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 19088 * This is achieved by rerunning the watchers until no changes are detected. The rerun 19089 * iteration limit is 10 to prevent an infinite loop deadlock. 19090 * 19091 * 19092 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 19093 * you can register a `watchExpression` function with no `listener`. (Be prepared for 19094 * multiple calls to your `watchExpression` because it will execute multiple times in a 19095 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 19096 * 19097 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 19098 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 19099 * watcher. In rare cases, this is undesirable because the listener is called when the result 19100 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 19101 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 19102 * listener was called due to initialization. 19103 * 19104 * 19105 * 19106 * # Example 19107 * ```js 19108 // let's assume that scope was dependency injected as the $rootScope 19109 var scope = $rootScope; 19110 scope.name = 'misko'; 19111 scope.counter = 0; 19112 19113 expect(scope.counter).toEqual(0); 19114 scope.$watch('name', function(newValue, oldValue) { 19115 scope.counter = scope.counter + 1; 19116 }); 19117 expect(scope.counter).toEqual(0); 19118 19119 scope.$digest(); 19120 // the listener is always called during the first $digest loop after it was registered 19121 expect(scope.counter).toEqual(1); 19122 19123 scope.$digest(); 19124 // but now it will not be called unless the value changes 19125 expect(scope.counter).toEqual(1); 19126 19127 scope.name = 'adam'; 19128 scope.$digest(); 19129 expect(scope.counter).toEqual(2); 19130 19131 19132
19133 // Using a function as a watchExpression 19134 var food; 19135 scope.foodCounter = 0; 19136 expect(scope.foodCounter).toEqual(0); 19137 scope.$watch( 19138 // This function returns the value being watched. It is called for each turn of the $digest loop 19139 function() { return food; }, 19140 // This is the change listener, called when the value returned from the above function changes 19141 function(newValue, oldValue) { 19142 if ( newValue !== oldValue ) { 19143 // Only increment the counter if the value changed 19144 scope.foodCounter = scope.foodCounter + 1; 19145 } 19146 } 19147 ); 19148 // No digest has been run so the counter will be zero 19149 expect(scope.foodCounter).toEqual(0); 19150 19151 // Run the digest but since food has not changed count will still be zero 19152 scope.$digest(); 19153 expect(scope.foodCounter).toEqual(0); 19154 19155 // Update food and run digest. Now the counter will increment 19156 food = 'cheeseburger'; 19157 scope.$digest(); 19158 expect(scope.foodCounter).toEqual(1); 19159 19160 * ``` 19161 * 19162 * 19163 * 19164 * @param {(function()|string)} watchExpression Expression that is evaluated on each 19165 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 19166 * a call to the `listener`. 19167 * 19168 * - `string`: Evaluated as {@link guide/expression expression} 19169 * - `function(scope)`: called with current `scope` as a parameter. 19170 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 19171 * of `watchExpression` changes. 19172 * 19173 * - `newVal` contains the current value of the `watchExpression` 19174 * - `oldVal` contains the previous value of the `watchExpression` 19175 * - `scope` refers to the current scope 19176 * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of 19177 * comparing for reference equality. 19178 * @returns {function()} Returns a deregistration function for this listener. 19179 */ 19180 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 19181 var get = $parse(watchExp); 19182 19183 if (get.$$watchDelegate) { 19184 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 19185 } 19186 var scope = this, 19187 array = scope.$$watchers, 19188 watcher = { 19189 fn: listener, 19190 last: initWatchVal, 19191 get: get, 19192 exp: prettyPrintExpression || watchExp, 19193 eq: !!objectEquality 19194 }; 19195 19196 lastDirtyWatch = null; 19197 19198 if (!isFunction(listener)) { 19199 watcher.fn = noop; 19200 } 19201 19202 if (!array) { 19203 array = scope.$$watchers = []; 19204 } 19205 // we use unshift since we use a while loop in $digest for speed. 19206 // the while loop reads in reverse order. 19207 array.unshift(watcher); 19208 incrementWatchersCount(this, 1); 19209 19210 return function deregisterWatch() { 19211 if (arrayRemove(array, watcher) >= 0) { 19212 incrementWatchersCount(scope, -1); 19213 } 19214 lastDirtyWatch = null; 19215 }; 19216 }, 19217 19218 /** 19219 * @ngdoc method 19220 * @name $rootScope.Scope#$watchGroup 19221 * @kind function 19222 * 19223 * @description 19224 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 19225 * If any one expression in the collection changes the `listener` is executed. 19226 * 19227 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 19228 * call to $digest() to see if any items changes. 19229 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 19230 * 19231 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 19232 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 19233 * 19234 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 19235 * expression in `watchExpressions` changes 19236 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 19237 * those of `watchExpression` 19238 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 19239 * those of `watchExpression` 19240 * The `scope` refers to the current scope. 19241 * @returns {function()} Returns a de-registration function for all listeners. 19242 */ 19243 $watchGroup: function(watchExpressions, listener) { 19244 var oldValues = new Array(watchExpressions.length); 19245 var newValues = new Array(watchExpressions.length); 19246 var deregisterFns = []; 19247 var self = this; 19248 var changeReactionScheduled = false;
vendor: 10,192 bytes, lines 19249-19511
19249 var firstRun = true; 19250 19251 if (!watchExpressions.length) { 19252 // No expressions means we call the listener ASAP 19253 var shouldCall = true; 19254 self.$evalAsync(function() { 19255 if (shouldCall) listener(newValues, newValues, self); 19256 }); 19257 return function deregisterWatchGroup() { 19258 shouldCall = false; 19259 }; 19260 } 19261 19262 if (watchExpressions.length === 1) { 19263 // Special case size of one 19264 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 19265 newValues[0] = value; 19266 oldValues[0] = oldValue; 19267 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 19268 }); 19269 } 19270 19271 forEach(watchExpressions, function(expr, i) { 19272 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 19273 newValues[i] = value; 19274 oldValues[i] = oldValue; 19275 if (!changeReactionScheduled) { 19276 changeReactionScheduled = true; 19277 self.$evalAsync(watchGroupAction); 19278 } 19279 }); 19280 deregisterFns.push(unwatchFn); 19281 }); 19282 19283 function watchGroupAction() { 19284 changeReactionScheduled = false; 19285 19286 if (firstRun) { 19287 firstRun = false; 19288 listener(newValues, newValues, self); 19289 } else { 19290 listener(newValues, oldValues, self); 19291 } 19292 } 19293 19294 return function deregisterWatchGroup() { 19295 while (deregisterFns.length) { 19296 deregisterFns.shift()(); 19297 } 19298 }; 19299 }, 19300 19301 19302 /** 19303 * @ngdoc method 19304 * @name $rootScope.Scope#$watchCollection 19305 * @kind function 19306 * 19307 * @description 19308 * Shallow watches the properties of an object and fires whenever any of the properties change 19309 * (for arrays, this implies watching the array items; for object maps, this implies watching 19310 * the properties). If a change is detected, the `listener` callback is fired. 19311 * 19312 * - The `obj` collection is observed via standard $watch operation and is examined on every 19313 * call to $digest() to see if any items have been added, removed, or moved. 19314 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 19315 * adding, removing, and moving items belonging to an object or array. 19316 * 19317 * 19318 * # Example 19319 * ```js 19320 $scope.names = ['igor', 'matias', 'misko', 'james']; 19321 $scope.dataCount = 4; 19322 19323 $scope.$watchCollection('names', function(newNames, oldNames) { 19324 $scope.dataCount = newNames.length; 19325 }); 19326 19327 expect($scope.dataCount).toEqual(4); 19328 $scope.$digest(); 19329 19330 //still at 4 ... no changes 19331 expect($scope.dataCount).toEqual(4); 19332 19333 $scope.names.pop(); 19334 $scope.$digest(); 19335 19336 //now there's been a change 19337 expect($scope.dataCount).toEqual(3); 19338 * ``` 19339 * 19340 * 19341 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 19342 * expression value should evaluate to an object or an array which is observed on each 19343 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 19344 * collection will trigger a call to the `listener`. 19345 * 19346 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 19347 * when a change is detected. 19348 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 19349 * - The `oldCollection` object is a copy of the former collection data. 19350 * Due to performance considerations, the`oldCollection` value is computed only if the 19351 * `listener` function declares two or more arguments. 19352 * - The `scope` argument refers to the current scope. 19353 * 19354 * @returns {function()} Returns a de-registration function for this listener. When the 19355 * de-registration function is executed, the internal watch operation is terminated. 19356 */ 19357 $watchCollection: function(obj, listener) { 19358 $watchCollectionInterceptor.$stateful = true; 19359 19360 var self = this; 19361 // the current value, updated on each dirty-check run 19362 var newValue; 19363 // a shallow copy of the newValue from the last dirty-check run, 19364 // updated to match newValue during dirty-check run 19365 var oldValue; 19366 // a shallow copy of the newValue from when the last change happened 19367 var veryOldValue; 19368 // only track veryOldValue if the listener is asking for it 19369 var trackVeryOldValue = (listener.length > 1); 19370 var changeDetected = 0; 19371 var changeDetector = $parse(obj, $watchCollectionInterceptor); 19372 var internalArray = []; 19373 var internalObject = {}; 19374 var initRun = true; 19375 var oldLength = 0; 19376 19377 function $watchCollectionInterceptor(_value) { 19378 newValue = _value; 19379 var newLength, key, bothNaN, newItem, oldItem; 19380 19381 // If the new value is undefined, then return undefined as the watch may be a one-time watch 19382 if (isUndefined(newValue)) return; 19383 19384 if (!isObject(newValue)) { // if primitive 19385 if (oldValue !== newValue) { 19386 oldValue = newValue; 19387 changeDetected++; 19388 } 19389 } else if (isArrayLike(newValue)) { 19390 if (oldValue !== internalArray) { 19391 // we are transitioning from something which was not an array into array. 19392 oldValue = internalArray; 19393 oldLength = oldValue.length = 0; 19394 changeDetected++; 19395 } 19396 19397 newLength = newValue.length; 19398 19399 if (oldLength !== newLength) { 19400 // if lengths do not match we need to trigger change notification 19401 changeDetected++; 19402 oldValue.length = oldLength = newLength; 19403 } 19404 // copy the items to oldValue and look for changes. 19405 for (var i = 0; i < newLength; i++) { 19406 oldItem = oldValue[i]; 19407 newItem = newValue[i]; 19408 19409 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 19410 if (!bothNaN && (oldItem !== newItem)) { 19411 changeDetected++; 19412 oldValue[i] = newItem; 19413 } 19414 } 19415 } else { 19416 if (oldValue !== internalObject) { 19417 // we are transitioning from something which was not an object into object. 19418 oldValue = internalObject = {}; 19419 oldLength = 0; 19420 changeDetected++; 19421 } 19422 // copy the items to oldValue and look for changes. 19423 newLength = 0; 19424 for (key in newValue) { 19425 if (hasOwnProperty.call(newValue, key)) { 19426 newLength++; 19427 newItem = newValue[key]; 19428 oldItem = oldValue[key]; 19429 19430 if (key in oldValue) { 19431 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 19432 if (!bothNaN && (oldItem !== newItem)) { 19433 changeDetected++; 19434 oldValue[key] = newItem; 19435 } 19436 } else { 19437 oldLength++; 19438 oldValue[key] = newItem; 19439 changeDetected++; 19440 } 19441 } 19442 } 19443 if (oldLength > newLength) { 19444 // we used to have more keys, need to find them and destroy them. 19445 changeDetected++; 19446 for (key in oldValue) { 19447 if (!hasOwnProperty.call(newValue, key)) { 19448 oldLength--; 19449 delete oldValue[key]; 19450 } 19451 } 19452 } 19453 } 19454 return changeDetected; 19455 } 19456 19457 function $watchCollectionAction() { 19458 if (initRun) { 19459 initRun = false; 19460 listener(newValue, newValue, self); 19461 } else { 19462 listener(newValue, veryOldValue, self); 19463 } 19464 19465 // make a copy for the next time a collection is changed 19466 if (trackVeryOldValue) { 19467 if (!isObject(newValue)) { 19468 //primitive 19469 veryOldValue = newValue; 19470 } else if (isArrayLike(newValue)) { 19471 veryOldValue = new Array(newValue.length); 19472 for (var i = 0; i < newValue.length; i++) { 19473 veryOldValue[i] = newValue[i]; 19474 } 19475 } else { // if object 19476 veryOldValue = {}; 19477 for (var key in newValue) { 19478 if (hasOwnProperty.call(newValue, key)) { 19479 veryOldValue[key] = newValue[key]; 19480 } 19481 } 19482 } 19483 } 19484 } 19485 19486 return this.$watch(changeDetector, $watchCollectionAction); 19487 }, 19488 19489 /** 19490 * @ngdoc method 19491 * @name $rootScope.Scope#$digest 19492 * @kind function 19493 * 19494 * @description 19495 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 19496 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 19497 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 19498 * until no more listeners are firing. This means that it is possible to get into an infinite 19499 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 19500 * iterations exceeds 10. 19501 * 19502 * Usually, you don't call `$digest()` directly in 19503 * {@link ng.directive:ngController controllers} or in 19504 * {@link ng.$compileProvider#directive directives}. 19505 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 19506 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 19507 * 19508 * If you want to be notified whenever `$digest()` is called, 19509 * you can register a `watchExpression` function with 19510 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 19511 *
19512 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 19513 * 19514 * # Example 19515 * ```js 19516 var scope = ...; 19517 scope.name = 'misko'; 19518 scope.counter = 0; 19519 19520 expect(scope.counter).toEqual(0); 19521 scope.$watch('name', function(newValue, oldValue) { 19522 scope.counter = scope.counter + 1; 19523 }); 19524 expect(scope.counter).toEqual(0); 19525 19526 scope.$digest(); 19527 // the listener is always called during the first $digest loop after it was registered 19528 expect(scope.counter).toEqual(1); 19529 19530 scope.$digest(); 19531 // but now it will not be called unless the value changes 19532 expect(scope.counter).toEqual(1); 19533 19534 scope.name = 'adam'; 19535 scope.$digest(); 19536 expect(scope.counter).toEqual(2); 19537 * ``` 19538 * 19539 */ 19540 $digest: function() { 19541 var watch, value, last, fn, get, 19542 watchers, 19543 length, 19544 dirty, ttl = TTL, 19545 next, current, target = this, 19546 watchLog = [], 19547 logIdx, asyncTask; 19548 19549 beginPhase('$digest'); 19550 // Check for changes to browser url that happened in sync before the call to $digest 19551 $browser.$$checkUrlChange(); 19552 19553 if (this === $rootScope && applyAsyncId !== null) { 19554 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 19555 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 19556 $browser.defer.cancel(applyAsyncId); 19557 flushApplyAsync(); 19558 } 19559 19560 lastDirtyWatch = null; 19561 19562 do { // "while dirty" loop 19563 dirty = false; 19564 current = target; 19565 19566 while (asyncQueue.length) { 19567 try { 19568 asyncTask = asyncQueue.shift(); 19569 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 19570 } catch (e) { 19571 $exceptionHandler(e); 19572 } 19573 lastDirtyWatch = null; 19574 } 19575 19576 traverseScopesLoop: 19577 do { // "traverse the scopes" loop 19578 if ((watchers = current.$$watchers)) { 19579 // process our watches 19580 length = watchers.length; 19581 while (length--) { 19582 try { 19583 watch = watchers[length]; 19584 // Most common watches are on primitives, in which case we can short 19585 // circuit it with === operator, only when === fails do we use .equals 19586 if (watch) { 19587 get = watch.get; 19588 if ((value = get(current)) !== (last = watch.last) && 19589 !(watch.eq 19590 ? equals(value, last) 19591 : (typeof value === 'number' && typeof last === 'number' 19592 && isNaN(value) && isNaN(last)))) { 19593 dirty = true; 19594 lastDirtyWatch = watch; 19595 watch.last = watch.eq ? copy(value, null) : value; 19596 fn = watch.fn; 19597 fn(value, ((last === initWatchVal) ? value : last), current); 19598 if (ttl < 5) { 19599 logIdx = 4 - ttl; 19600 if (!watchLog[logIdx]) watchLog[logIdx] = []; 19601 watchLog[logIdx].push({ 19602 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 19603 newVal: value, 19604 oldVal: last 19605 }); 19606 } 19607 } else if (watch === lastDirtyWatch) { 19608 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 19609 // have already been tested. 19610 dirty = false; 19611 break traverseScopesLoop; 19612 } 19613 } 19614 } catch (e) { 19615 $exceptionHandler(e); 19616 } 19617 } 19618 } 19619 19620 // Insanity Warning: scope depth-first traversal 19621 // yes, this code is a bit crazy, but it works and we have tests to prove it! 19622 // this piece should be kept in sync with the traversal in $broadcast 19623 if (!(next = ((current.$$watchersCount && current.$$childHead) || 19624 (current !== target && current.$$nextSibling)))) { 19625 while (current !== target && !(next = current.$$nextSibling)) { 19626 current = current.$parent; 19627 } 19628 } 19629 } while ((current = next)); 19630 19631 // `break traverseScopesLoop;` takes us to here 19632 19633 if ((dirty || asyncQueue.length) && !(ttl--)) { 19634 clearPhase(); 19635 throw $rootScopeMinErr('infdig', 19636 '{0} $digest() iterations reached. Aborting!\n' + 19637 'Watchers fired in the last 5 iterations: {1}', 19638 TTL, watchLog); 19639 } 19640 19641 }
vendor: 4,632 bytes, lines 19641-19760
19641 while (dirty || asyncQueue.length); 19642 19643 clearPhase(); 19644 19645 while (postDigestQueue.length) { 19646 try { 19647 postDigestQueue.shift()(); 19648 } catch (e) { 19649 $exceptionHandler(e); 19650 } 19651 } 19652 }, 19653 19654 19655 /** 19656 * @ngdoc event 19657 * @name $rootScope.Scope#$destroy 19658 * @eventType broadcast on scope being destroyed 19659 * 19660 * @description 19661 * Broadcasted when a scope and its children are being destroyed. 19662 * 19663 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 19664 * clean up DOM bindings before an element is removed from the DOM. 19665 */ 19666 19667 /** 19668 * @ngdoc method 19669 * @name $rootScope.Scope#$destroy 19670 * @kind function 19671 * 19672 * @description 19673 * Removes the current scope (and all of its children) from the parent scope. Removal implies 19674 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 19675 * propagate to the current scope and its children. Removal also implies that the current 19676 * scope is eligible for garbage collection. 19677 * 19678 * The `$destroy()` is usually used by directives such as 19679 * {@link ng.directive:ngRepeat ngRepeat} for managing the 19680 * unrolling of the loop. 19681 * 19682 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 19683 * Application code can register a `$destroy` event handler that will give it a chance to 19684 * perform any necessary cleanup. 19685 * 19686 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 19687 * clean up DOM bindings before an element is removed from the DOM. 19688 */ 19689 $destroy: function() { 19690 // We can't destroy a scope that has been already destroyed. 19691 if (this.$$destroyed) return; 19692 var parent = this.$parent; 19693 19694 this.$broadcast('$destroy'); 19695 this.$$destroyed = true; 19696 19697 if (this === $rootScope) { 19698 //Remove handlers attached to window when $rootScope is removed 19699 $browser.$$applicationDestroyed(); 19700 } 19701 19702 incrementWatchersCount(this, -this.$$watchersCount); 19703 for (var eventName in this.$$listenerCount) { 19704 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 19705 } 19706 19707 // sever all the references to parent scopes (after this cleanup, the current scope should 19708 // not be retained by any of our references and should be eligible for garbage collection) 19709 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 19710 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 19711 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 19712 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 19713 19714 // Disable listeners, watchers and apply/digest methods 19715 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 19716 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 19717 this.$$listeners = {}; 19718 19719 // Disconnect the next sibling to prevent `cleanUpScope` destroying those too 19720 this.$$nextSibling = null; 19721 cleanUpScope(this); 19722 }, 19723 19724 /** 19725 * @ngdoc method 19726 * @name $rootScope.Scope#$eval 19727 * @kind function 19728 * 19729 * @description 19730 * Executes the `expression` on the current scope and returns the result. Any exceptions in 19731 * the expression are propagated (uncaught). This is useful when evaluating Angular 19732 * expressions. 19733 * 19734 * # Example 19735 * ```js 19736 var scope = ng.$rootScope.Scope(); 19737 scope.a = 1; 19738 scope.b = 2; 19739 19740 expect(scope.$eval('a+b')).toEqual(3); 19741 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 19742 * ``` 19743 * 19744 * @param {(string|function())=} expression An angular expression to be executed. 19745 * 19746 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19747 * - `function(scope)`: execute the function with the current `scope` parameter. 19748 * 19749 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 19750 * @returns {*} The result of evaluating the expression. 19751 */ 19752 $eval: function(expr, locals) { 19753 return $parse(expr)(this, locals); 19754 }, 19755 19756 /** 19757 * @ngdoc method 19758 * @name $rootScope.Scope#$evalAsync 19759 * @kind function 19760 *
19761 * @description 19762 * Executes the expression on the current scope at a later point in time. 19763 * 19764 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 19765 * that: 19766 * 19767 * - it will execute after the function that scheduled the evaluation (preferably before DOM 19768 * rendering). 19769 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 19770 * `expression` execution. 19771 * 19772 * Any exceptions from the execution of the expression are forwarded to the 19773 * {@link ng.$exceptionHandler $exceptionHandler} service. 19774 * 19775 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 19776 * will be scheduled. However, it is encouraged to always call code that changes the model 19777 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 19778 * 19779 * @param {(string|function())=} expression An angular expression to be executed. 19780 * 19781 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19782 * - `function(scope)`: execute the function with the current `scope` parameter. 19783 * 19784 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 19785 */ 19786 $evalAsync: function(expr, locals) { 19787 // if we are outside of an $digest loop and this is the first time we are scheduling async 19788 // task also schedule async auto-flush 19789 if (!$rootScope.$$phase && !asyncQueue.length) { 19790 $browser.defer(function() { 19791 if (asyncQueue.length) { 19792 $rootScope.$digest(); 19793 } 19794 }); 19795 } 19796 19797 asyncQueue.push({scope: this, expression: $parse(expr), locals: locals}); 19798 }, 19799 19800 $$postDigest: function(fn) { 19801 postDigestQueue.push(fn); 19802 }, 19803 19804 /** 19805 * @ngdoc method 19806 * @name $rootScope.Scope#$apply 19807 * @kind function 19808 * 19809 * @description 19810 * `$apply()` is used to execute an expression in angular from outside of the angular 19811 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 19812 * Because we are calling into the angular framework we need to perform proper scope life 19813 * cycle of {@link ng.$exceptionHandler exception handling}, 19814 * {@link ng.$rootScope.Scope#$digest executing watches}. 19815 * 19816 * ## Life cycle 19817 * 19818 * # Pseudo-Code of `$apply()` 19819 * ```js 19820 function $apply(expr) { 19821 try { 19822 return $eval(expr); 19823 } catch (e) { 19824 $exceptionHandler(e); 19825 } finally { 19826 $root.$digest(); 19827 } 19828 } 19829 * ``` 19830 * 19831 * 19832 * Scope's `$apply()` method transitions through the following stages: 19833 * 19834 * 1. The {@link guide/expression expression} is executed using the 19835 * {@link ng.$rootScope.Scope#$eval $eval()} method. 19836 * 2. Any exceptions from the execution of the expression are forwarded to the 19837 * {@link ng.$exceptionHandler $exceptionHandler} service. 19838 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 19839 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 19840 * 19841 * 19842 * @param {(string|function())=} exp An angular expression to be executed. 19843 * 19844 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19845 * - `function(scope)`: execute the function with current `scope` parameter. 19846 * 19847 * @returns {*} The result of evaluating the expression. 19848 */ 19849 $apply: function(expr) { 19850 try { 19851 beginPhase('$apply'); 19852 try { 19853 return this.$eval(expr); 19854 } finally { 19855 clearPhase(); 19856 } 19857 } catch (e) { 19858 $exceptionHandler(e); 19859 } finally { 19860 try { 19861 $rootScope.$digest(); 19862 } catch (e) { 19863 $exceptionHandler(e); 19864 throw e; 19865 } 19866 } 19867 }, 19868 19869 /** 19870 * @ngdoc method 19871 * @name $rootScope.Scope#$applyAsync 19872 * @kind function 19873 *
19874 * @description 19875 * Schedule the invocation of $apply to occur at a later time. The actual time difference 19876 * varies across browsers, but is typically around ~10 milliseconds. 19877 * 19878 * This can be used to queue up multiple expressions which need to be evaluated in the same 19879 * digest. 19880 * 19881 * @param {(string|function())=} exp An angular expression to be executed. 19882 * 19883 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 19884 * - `function(scope)`: execute the function with current `scope` parameter. 19885 */ 19886 $applyAsync: function(expr) { 19887 var scope = this; 19888 expr && applyAsyncQueue.push($applyAsyncExpression); 19889 expr = $parse(expr); 19890 scheduleApplyAsync(); 19891 19892 function $applyAsyncExpression() { 19893 scope.$eval(expr); 19894 } 19895 }, 19896 19897 /** 19898 * @ngdoc method 19899 * @name $rootScope.Scope#$on 19900 * @kind function 19901 * 19902 * @description 19903 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 19904 * discussion of event life cycle. 19905 * 19906 * The event listener function format is: `function(event, args...)`. The `event` object 19907 * passed into the listener has the following attributes: 19908 * 19909 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 19910 * `$broadcast`-ed. 19911 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 19912 * event propagates through the scope hierarchy, this property is set to null. 19913 * - `name` - `{string}`: name of the event. 19914 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 19915 * further event propagation (available only for events that were `$emit`-ed). 19916 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 19917 * to true. 19918 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 19919 * 19920 * @param {string} name Event name to listen on. 19921 * @param {function(event, ...args)} listener Function to call when the event is emitted. 19922 * @returns {function()} Returns a deregistration function for this listener. 19923 */ 19924 $on: function(name, listener) { 19925 var namedListeners = this.$$listeners[name]; 19926 if (!namedListeners) { 19927 this.$$listeners[name] = namedListeners = []; 19928 } 19929 namedListeners.push(listener); 19930 19931 var current = this; 19932 do { 19933 if (!current.$$listenerCount[name]) { 19934 current.$$listenerCount[name] = 0; 19935 } 19936 current.$$listenerCount[name]++; 19937 } while ((current = current.$parent)); 19938 19939 var self = this; 19940 return function() { 19941 var indexOfListener = namedListeners.indexOf(listener); 19942 if (indexOfListener !== -1) { 19943 namedListeners[indexOfListener] = null; 19944 decrementListenerCount(self, 1, name); 19945 } 19946 }; 19947 }, 19948 19949 19950 /** 19951 * @ngdoc method 19952 * @name $rootScope.Scope#$emit 19953 * @kind function 19954 * 19955 * @description 19956 * Dispatches an event `name` upwards through the scope hierarchy notifying the 19957 * registered {@link ng.$rootScope.Scope#$on} listeners. 19958 * 19959 * The event life cycle starts at the scope on which `$emit` was called. All 19960 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 19961 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 19962 * registered listeners along the way. The event will stop propagating if one of the listeners 19963 * cancels it. 19964 * 19965 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 19966 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 19967 * 19968 * @param {string} name Event name to emit. 19969 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 19970 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 19971 */ 19972 $emit: function(name, args) { 19973 var empty = [], 19974 namedListeners, 19975 scope = this, 19976 stopPropagation = false, 19977 event = { 19978 name: name, 19979 targetScope: scope, 19980 stopPropagation: function() {stopPropagation = true;}, 19981 preventDefault: function() { 19982 event.defaultPrevented = true; 19983 }, 19984 defaultPrevented: false 19985 }, 19986 listenerArgs = concat([event], arguments, 1), 19987 i, length; 19988 19989 do { 19990 namedListeners = scope.$$listeners[name] || empty; 19991 event.currentScope = scope; 19992 for (i = 0, length = namedListeners.length; i < length; i++) { 19993 19994 // if listeners were deregistered, defragment the array 19995 if (!namedListeners[i]) { 19996 namedListeners.splice(i, 1); 19997 i--; 19998 length--; 19999 continue; 20000 } 20001 try { 20002 //allow all listeners attached to the current scope to run 20003 namedListeners[i].apply(null, listenerArgs); 20004 } catch (e) { 20005 $exceptionHandler(e); 20006 } 20007 } 20008 //if any listener on the current scope stops propagation, prevent bubbling 20009 if (stopPropagation) { 20010 event.currentScope = null; 20011 return event; 20012 } 20013 //traverse upwards 20014 scope = scope.$parent; 20015 } while (scope); 20016 20017 event.currentScope = null; 20018 20019 return event; 20020 }, 20021 20022 20023 /** 20024 * @ngdoc method 20025 * @name $rootScope.Scope#$broadcast 20026 * @kind function 20027 *
20028 * @description 20029 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 20030 * registered {@link ng.$rootScope.Scope#$on} listeners. 20031 * 20032 * The event life cycle starts at the scope on which `$broadcast` was called. All 20033 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 20034 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 20035 * scope and calls all registered listeners along the way. The event cannot be canceled. 20036 * 20037 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 20038 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 20039 * 20040 * @param {string} name Event name to broadcast. 20041 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 20042 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 20043 */ 20044 $broadcast: function(name, args) { 20045 var target = this, 20046 current = target, 20047 next = target, 20048 event = { 20049 name: name, 20050 targetScope: target, 20051 preventDefault: function() { 20052 event.defaultPrevented = true; 20053 }, 20054 defaultPrevented: false 20055 }; 20056 20057 if (!target.$$listenerCount[name]) return event; 20058 20059 var listenerArgs = concat([event], arguments, 1), 20060 listeners, i, length; 20061 20062 //down while you can, then up and next sibling or up and next sibling until back at root 20063 while ((current = next)) { 20064 event.currentScope = current; 20065 listeners = current.$$listeners[name] || []; 20066 for (i = 0, length = listeners.length; i < length; i++) { 20067 // if listeners were deregistered, defragment the array 20068 if (!listeners[i]) { 20069 listeners.splice(i, 1); 20070 i--; 20071 length--; 20072 continue; 20073 } 20074 20075 try { 20076 listeners[i].apply(null, listenerArgs); 20077 } catch (e) { 20078 $exceptionHandler(e); 20079 } 20080 } 20081 20082 // Insanity Warning: scope depth-first traversal 20083 // yes, this code is a bit crazy, but it works and we have tests to prove it! 20084 // this piece should be kept in sync with the traversal in $digest 20085 // (though it differs due to having the extra check for $$listenerCount) 20086 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 20087 (current !== target && current.$$nextSibling)))) { 20088 while (current !== target && !(next = current.$$nextSibling)) { 20089 current = current.$parent; 20090 } 20091 } 20092 } 20093 20094 event.currentScope = null; 20095 return event; 20096 } 20097 }; 20098 20099 var $rootScope = new Scope(); 20100 20101 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 20102 var asyncQueue = $rootScope.$$asyncQueue = []; 20103 var postDigestQueue = $rootScope.$$postDigestQueue = []; 20104 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 20105 20106 return $rootScope; 20107 20108 20109 function beginPhase(phase) { 20110 if ($rootScope.$$phase) { 20111 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 20112 } 20113 20114 $rootScope.$$phase = phase; 20115 } 20116 20117 function clearPhase() { 20118 $rootScope.$$phase = null; 20119 } 20120 20121 function incrementWatchersCount(current, count) { 20122 do { 20123 current.$$watchersCount += count; 20124 } while ((current = current.$parent)); 20125 } 20126 20127 function decrementListenerCount(current, count, name) { 20128 do { 20129 current.$$listenerCount[name] -= count; 20130 20131 if (current.$$listenerCount[name] === 0) { 20132 delete current.$$listenerCount[name]; 20133 } 20134 } while ((current = current.$parent)); 20135 } 20136 20137 /** 20138 * function used as an initial value for watchers. 20139 * because it's unique we can easily tell it apart from other values 20140 */ 20141 function initWatchVal() {} 20142 20143 function flushApplyAsync() {
vendor: 5,358 bytes, lines 20144-20286
20144 while (applyAsyncQueue.length) { 20145 try { 20146 applyAsyncQueue.shift()(); 20147 } catch (e) { 20148 $exceptionHandler(e); 20149 } 20150 } 20151 applyAsyncId = null; 20152 } 20153 20154 function scheduleApplyAsync() { 20155 if (applyAsyncId === null) { 20156 applyAsyncId = $browser.defer(function() { 20157 $rootScope.$apply(flushApplyAsync); 20158 }); 20159 } 20160 } 20161 }]; 20162} 20163 20164/** 20165 * @ngdoc service 20166 * @name $rootElement 20167 * 20168 * @description 20169 * The root element of Angular application. This is either the element where {@link 20170 * ng.directive:ngApp ngApp} was declared or the element passed into 20171 * {@link angular.bootstrap}. The element represents the root element of application. It is also the 20172 * location where the application's {@link auto.$injector $injector} service gets 20173 * published, and can be retrieved using `$rootElement.injector()`. 20174 */ 20175 20176 20177// the implementation is in angular.bootstrap 20178 20179/** 20180 * @description 20181 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 20182 */ 20183function $$SanitizeUriProvider() { 20184 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 20185 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 20186 20187 /** 20188 * @description 20189 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 20190 * urls during a[href] sanitization. 20191 * 20192 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 20193 * 20194 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 20195 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 20196 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 20197 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 20198 * 20199 * @param {RegExp=} regexp New regexp to whitelist urls with. 20200 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 20201 * chaining otherwise. 20202 */ 20203 this.aHrefSanitizationWhitelist = function(regexp) { 20204 if (isDefined(regexp)) { 20205 aHrefSanitizationWhitelist = regexp; 20206 return this; 20207 } 20208 return aHrefSanitizationWhitelist; 20209 }; 20210 20211 20212 /** 20213 * @description 20214 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 20215 * urls during img[src] sanitization. 20216 * 20217 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 20218 * 20219 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 20220 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 20221 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 20222 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 20223 * 20224 * @param {RegExp=} regexp New regexp to whitelist urls with. 20225 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 20226 * chaining otherwise. 20227 */ 20228 this.imgSrcSanitizationWhitelist = function(regexp) { 20229 if (isDefined(regexp)) { 20230 imgSrcSanitizationWhitelist = regexp; 20231 return this; 20232 } 20233 return imgSrcSanitizationWhitelist; 20234 }; 20235 20236 this.$get = function() { 20237 return function sanitizeUri(uri, isImage) { 20238 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 20239 var normalizedVal; 20240 normalizedVal = urlResolve(uri).href; 20241 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 20242 return 'unsafe:' + normalizedVal; 20243 } 20244 return uri; 20245 }; 20246 }; 20247} 20248 20249/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 20250 * Any commits to this file should be reviewed with security in mind. * 20251 * Changes to this file can potentially create security vulnerabilities. * 20252 * An approval from 2 Core members with history of modifying * 20253 * this file is required. * 20254 * * 20255 * Does the change somehow allow for arbitrary javascript to be executed? * 20256 * Or allows for someone to change the prototype of built-in objects? * 20257 * Or gives undesired access to variables likes document or window? * 20258 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 20259 20260var $sceMinErr = minErr('$sce'); 20261 20262var SCE_CONTEXTS = { 20263 HTML: 'html', 20264 CSS: 'css', 20265 URL: 'url', 20266 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 20267 // url. (e.g. ng-include, script src, templateUrl) 20268 RESOURCE_URL: 'resourceUrl', 20269 JS: 'js' 20270}; 20271 20272// Helper functions follow. 20273 20274function adjustMatcher(matcher) { 20275 if (matcher === 'self') { 20276 return matcher; 20277 } else if (isString(matcher)) { 20278 // Strings match exactly except for 2 wildcards - '*' and '**'. 20279 // '*' matches any character except those from the set ':/.?&'. 20280 // '**' matches any character (like .* in a RegExp). 20281 // More than 2 *'s raises an error as it's ill defined. 20282 if (matcher.indexOf('***') > -1) { 20283 throw $sceMinErr('iwcard', 20284 'Illegal sequence *** in string matcher. String: {0}', matcher); 20285 } 20286 matcher = escapeForRegexp(matcher).
20287 replace('\\*\\*', '.*'). 20288 replace('\\*', '[^:/.?&;]*'); 20289 return new RegExp('^' + matcher + '$'); 20290 } else if (isRegExp(matcher)) { 20291 // The only other type of matcher allowed is a Regexp. 20292 // Match entire URL / disallow partial matches. 20293 // Flags are reset (i.e. no global, ignoreCase or multiline) 20294 return new RegExp('^' + matcher.source + '$'); 20295 } else { 20296 throw $sceMinErr('imatcher', 20297 'Matchers may only be "self", string patterns or RegExp objects'); 20298 } 20299} 20300 20301 20302function adjustMatchers(matchers) { 20303 var adjustedMatchers = []; 20304 if (isDefined(matchers)) { 20305 forEach(matchers, function(matcher) { 20306 adjustedMatchers.push(adjustMatcher(matcher)); 20307 }); 20308 } 20309 return adjustedMatchers; 20310} 20311 20312 20313/** 20314 * @ngdoc service 20315 * @name $sceDelegate 20316 * @kind function 20317 * 20318 * @description 20319 * 20320 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 20321 * Contextual Escaping (SCE)} services to AngularJS. 20322 * 20323 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 20324 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 20325 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 20326 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 20327 * work because `$sce` delegates to `$sceDelegate` for these operations. 20328 * 20329 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 20330 * 20331 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 20332 * can override it completely to change the behavior of `$sce`, the common case would 20333 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 20334 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 20335 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 20336 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 20337 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 20338 */ 20339 20340/** 20341 * @ngdoc provider 20342 * @name $sceDelegateProvider 20343 * @description 20344 * 20345 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 20346 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 20347 * that the URLs used for sourcing Angular templates are safe. Refer {@link 20348 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 20349 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 20350 * 20351 * For the general details about this service in Angular, read the main page for {@link ng.$sce 20352 * Strict Contextual Escaping (SCE)}. 20353 * 20354 * **Example**: Consider the following case. <a name="example"></a> 20355 * 20356 * - your app is hosted at url `http://myapp.example.com/` 20357 * - but some of your templates are hosted on other domains you control such as 20358 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 20359 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 20360 * 20361 * Here is what a secure configuration for this scenario might look like: 20362 * 20363 * ``` 20364 * angular.module('myApp', []).config(function($sceDelegateProvider) { 20365 * $sceDelegateProvider.resourceUrlWhitelist([ 20366 * // Allow same origin resource loads. 20367 * 'self', 20368 * // Allow loading from our assets domain. Notice the difference between * and **. 20369 * 'http://srv*.assets.example.com/**' 20370 * ]); 20371 * 20372 * // The blacklist overrides the whitelist so the open redirect here is blocked. 20373 * $sceDelegateProvider.resourceUrlBlacklist([ 20374 * 'http://myapp.example.com/clickThru**' 20375 * ]); 20376 * }); 20377 * ``` 20378 */ 20379 20380function $SceDelegateProvider() { 20381 this.SCE_CONTEXTS = SCE_CONTEXTS; 20382 20383 // Resource URLs can also be trusted by policy. 20384 var resourceUrlWhitelist = ['self'], 20385 resourceUrlBlacklist = []; 20386 20387 /** 20388 * @ngdoc method 20389 * @name $sceDelegateProvider#resourceUrlWhitelist 20390 * @kind function 20391 * 20392 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 20393 * provided. This must be an array or null. A snapshot of this array is used so further 20394 * changes to the array are ignored. 20395 * 20396 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 20397 * allowed in this array.
20398 * 20399 * <div class="alert alert-warning"> 20400 * **Note:** an empty whitelist array will block all URLs! 20401 * </div> 20402 * 20403 * @return {Array} the currently set whitelist array. 20404 * 20405 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 20406 * same origin resource requests. 20407 * 20408 * @description 20409 * Sets/Gets the whitelist of trusted resource URLs. 20410 */ 20411 this.resourceUrlWhitelist = function(value) { 20412 if (arguments.length) { 20413 resourceUrlWhitelist = adjustMatchers(value); 20414 } 20415 return resourceUrlWhitelist; 20416 }; 20417 20418 /** 20419 * @ngdoc method 20420 * @name $sceDelegateProvider#resourceUrlBlacklist 20421 * @kind function 20422 * 20423 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 20424 * provided. This must be an array or null. A snapshot of this array is used so further 20425 * changes to the array are ignored. 20426 * 20427 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 20428 * allowed in this array. 20429 * 20430 * The typical usage for the blacklist is to **block 20431 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 20432 * these would otherwise be trusted but actually return content from the redirected domain. 20433 * 20434 * Finally, **the blacklist overrides the whitelist** and has the final say. 20435 * 20436 * @return {Array} the currently set blacklist array. 20437 * 20438 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 20439 * is no blacklist.) 20440 * 20441 * @description 20442 * Sets/Gets the blacklist of trusted resource URLs. 20443 */ 20444 20445 this.resourceUrlBlacklist = function(value) { 20446 if (arguments.length) { 20447 resourceUrlBlacklist = adjustMatchers(value); 20448 } 20449 return resourceUrlBlacklist; 20450 }; 20451 20452 this.$get = ['$injector', function($injector) { 20453 20454 var htmlSanitizer = function htmlSanitizer(html) { 20455 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 20456 }; 20457 20458 if ($injector.has('$sanitize')) { 20459 htmlSanitizer = $injector.get('$sanitize'); 20460 } 20461 20462 20463 function matchUrl(matcher, parsedUrl) { 20464 if (matcher === 'self') { 20465 return urlIsSameOrigin(parsedUrl); 20466 } else { 20467 // definitely a regex. See adjustMatchers() 20468 return !!matcher.exec(parsedUrl.href); 20469 } 20470 } 20471 20472 function isResourceUrlAllowedByPolicy(url) { 20473 var parsedUrl = urlResolve(url.toString()); 20474 var i, n, allowed = false; 20475 // Ensure that at least one item from the whitelist allows this url. 20476 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 20477 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 20478 allowed = true; 20479 break; 20480 } 20481 } 20482 if (allowed) { 20483 // Ensure that no item from the blacklist blocked this url. 20484 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 20485 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 20486 allowed = false; 20487 break; 20488 } 20489 } 20490 } 20491 return allowed; 20492 } 20493 20494 function generateHolderType(Base) { 20495 var holderType = function TrustedValueHolderType(trustedValue) { 20496 this.$$unwrapTrustedValue = function() { 20497 return trustedValue; 20498 }; 20499 }; 20500 if (Base) { 20501 holderType.prototype = new Base(); 20502 } 20503 holderType.prototype.valueOf = function sceValueOf() { 20504 return this.$$unwrapTrustedValue(); 20505 }; 20506 holderType.prototype.toString = function sceToString() { 20507 return this.$$unwrapTrustedValue().toString(); 20508 }; 20509 return holderType; 20510 } 20511 20512 var trustedValueHolderBase = generateHolderType(), 20513 byType = {}; 20514 20515 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 20516 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 20517 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 20518 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 20519 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 20520 20521 /** 20522 * @ngdoc method 20523 * @name $sceDelegate#trustAs 20524 *
20525 * @description 20526 * Returns an object that is trusted by angular for use in specified strict 20527 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 20528 * attribute interpolation, any dom event binding attribute interpolation 20529 * such as for onclick, etc.) that uses the provided value. 20530 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 20531 * 20532 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 20533 * resourceUrl, html, js and css. 20534 * @param {*} value The value that that should be considered trusted/safe. 20535 * @returns {*} A value that can be used to stand in for the provided `value` in places 20536 * where Angular expects a $sce.trustAs() return value. 20537 */ 20538 function trustAs(type, trustedValue) { 20539 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 20540 if (!Constructor) { 20541 throw $sceMinErr('icontext', 20542 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 20543 type, trustedValue); 20544 } 20545 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 20546 return trustedValue; 20547 } 20548 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 20549 // mutable objects, we ensure here that the value passed in is actually a string. 20550 if (typeof trustedValue !== 'string') { 20551 throw $sceMinErr('itype', 20552 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 20553 type); 20554 } 20555 return new Constructor(trustedValue); 20556 } 20557 20558 /** 20559 * @ngdoc method 20560 * @name $sceDelegate#valueOf 20561 * 20562 * @description 20563 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 20564 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 20565 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 20566 * 20567 * If the passed parameter is not a value that had been returned by {@link 20568 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 20569 * 20570 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 20571 * call or anything else. 20572 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 20573 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 20574 * `value` unchanged. 20575 */ 20576 function valueOf(maybeTrusted) { 20577 if (maybeTrusted instanceof trustedValueHolderBase) { 20578 return maybeTrusted.$$unwrapTrustedValue(); 20579 } else { 20580 return maybeTrusted; 20581 } 20582 } 20583 20584 /** 20585 * @ngdoc method 20586 * @name $sceDelegate#getTrusted 20587 * 20588 * @description 20589 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 20590 * returns the originally supplied value if the queried context type is a supertype of the 20591 * created type. If this condition isn't satisfied, throws an exception. 20592 * 20593 * <div class="alert alert-danger"> 20594 * Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting 20595 * (XSS) vulnerability in your application. 20596 * </div> 20597 * 20598 * @param {string} type The kind of context in which this value is to be used. 20599 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 20600 * `$sceDelegate.trustAs`} call. 20601 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 20602 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 20603 */ 20604 function getTrusted(type, maybeTrusted) { 20605 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 20606 return maybeTrusted; 20607 } 20608 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 20609 if (constructor && maybeTrusted instanceof constructor) { 20610 return maybeTrusted.$$unwrapTrustedValue(); 20611 } 20612 // If we get here, then we may only take one of two actions. 20613 // 1. sanitize the value for the requested type, or 20614 // 2. throw an exception. 20615 if (type === SCE_CONTEXTS.RESOURCE_URL) { 20616 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 20617 return maybeTrusted; 20618 } else { 20619 throw $sceMinErr('insecurl', 20620 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 20621 maybeTrusted.toString()); 20622 } 20623 } else if (type === SCE_CONTEXTS.HTML) { 20624 return htmlSanitizer(maybeTrusted); 20625 } 20626 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 20627 } 20628 20629 return { trustAs: trustAs, 20630 getTrusted: getTrusted,
20631 valueOf: valueOf }; 20632 }]; 20633} 20634 20635 20636/** 20637 * @ngdoc provider 20638 * @name $sceProvider 20639 * @description 20640 * 20641 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 20642 * - enable/disable Strict Contextual Escaping (SCE) in a module 20643 * - override the default implementation with a custom delegate 20644 * 20645 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 20646 */ 20647 20648/* jshint maxlen: false*/ 20649 20650/** 20651 * @ngdoc service 20652 * @name $sce 20653 * @kind function 20654 * 20655 * @description 20656 * 20657 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 20658 * 20659 * # Strict Contextual Escaping 20660 * 20661 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 20662 * contexts to result in a value that is marked as safe to use for that context. One example of 20663 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 20664 * to these contexts as privileged or SCE contexts. 20665 * 20666 * As of version 1.2, Angular ships with SCE enabled by default. 20667 * 20668 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 20669 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 20670 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
20671 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 20672 * to the top of your HTML document. 20673 * 20674 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 20675 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 20676 * 20677 * Here's an example of a binding in a privileged context: 20678 * 20679 * ``` 20680 * <input ng-model="userHtml" aria-label="User input"> 20681 * <div ng-bind-html="userHtml"></div> 20682 * ``` 20683 * 20684 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 20685 * disabled, this application allows the user to render arbitrary HTML into the DIV. 20686 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 20687 * bindings. (HTML is just one example of a context where rendering user controlled input creates 20688 * security vulnerabilities.) 20689 * 20690 * For the case of HTML, you might use a library, either on the client side, or on the server side, 20691 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 20692 * 20693 * How would you ensure that every place that used these types of bindings was bound to a value that 20694 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 20695 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 20696 * properties/fields and forgot to update the binding to the sanitized value? 20697 * 20698 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 20699 * determine that something explicitly says it's safe to use a value for binding in that
20700 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 20701 * for those values that you can easily tell are safe - because they were received from your server, 20702 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 20703 * allowing only the files in a specific directory to do this. Ensuring that the internal API 20704 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 20705 * 20706 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 20707 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 20708 * obtain values that will be accepted by SCE / privileged contexts. 20709 * 20710 * 20711 * ## How does it work? 20712 * 20713 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 20714 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 20715 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 20716 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 20717 * 20718 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 20719 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 20720 * simplified): 20721 * 20722 * ``` 20723 * var ngBindHtmlDirective = ['$sce', function($sce) { 20724 * return function(scope, element, attr) { 20725 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 20726 * element.html(value || ''); 20727 * }); 20728 * }; 20729 * }]; 20730 * ``` 20731 * 20732 * ## Impact on loading templates 20733 * 20734 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 20735 * `templateUrl`'s specified by {@link guide/directive directives}. 20736 * 20737 * By default, Angular only loads templates from the same domain and protocol as the application 20738 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 20739 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 20740 * protocols, you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 20741 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 20742 * 20743 * *Please note*: 20744 * The browser's 20745 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 20746 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 20747 * policy apply in addition to this and may further restrict whether the template is successfully 20748 * loaded. This means that without the right CORS policy, loading templates from a different domain 20749 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 20750 * browsers. 20751 * 20752 * ## This feels like too much overhead 20753 * 20754 * It's important to remember that SCE only applies to interpolation expressions. 20755 * 20756 * If your expressions are constant literals, they're automatically trusted and you don't need to 20757 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 20758 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 20759 * 20760 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 20761 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 20762 * 20763 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 20764 * templates in `ng-include` from your application's domain without having to even know about SCE. 20765 * It blocks loading templates from other domains or loading templates over http from an https 20766 * served document. You can change these by setting your own custom {@link 20767 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 20768 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 20769 * 20770 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 20771 * application that's secure and can be audited to verify that with much more ease than bolting 20772 * security onto an application later. 20773 * 20774 * <a name="contexts"></a> 20775 * ## What trusted context types are supported? 20776 * 20777 * | Context | Notes | 20778 * |---------------------|----------------| 20779 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | 20780 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 20781 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. | 20782 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | 20783 * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | 20784 * 20785 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 20786 *
20787 * Each element in these arrays must be one of the following: 20788 * 20789 * - **'self'** 20790 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 20791 * domain** as the application document using the **same protocol**. 20792 * - **String** (except the special value `'self'`) 20793 * - The string is matched against the full *normalized / absolute URL* of the resource 20794 * being tested (substring matches are not good enough.) 20795 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 20796 * match themselves. 20797 * - `*`: matches zero or more occurrences of any character other than one of the following 6 20798 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use 20799 * in a whitelist. 20800 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 20801 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 20802 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 20803 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 20804 * http://foo.example.com/templates/**). 20805 * - **RegExp** (*see caveat below*) 20806 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 20807 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 20808 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 20809 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 20810 * small number of cases. A `.` character in the regex used when matching the scheme or a 20811 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 20812 * is highly recommended to use the string patterns and only fall back to regular expressions 20813 * as a last resort. 20814 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 20815 * matched against the **entire** *normalized / absolute URL* of the resource being tested 20816 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 20817 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 20818 * - If you are generating your JavaScript from some other templating engine (not 20819 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 20820 * remember to escape your regular expression (and be aware that you might need more than 20821 * one level of escaping depending on your templating engine and the way you interpolated 20822 * the value.) Do make use of your platform's escaping mechanism as it might be good 20823 * enough before coding your own. E.g. Ruby has 20824 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 20825 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 20826 * Javascript lacks a similar built in function for escaping. Take a look at Google 20827 * Closure library's [goog.string.regExpEscape(s)]( 20828 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 20829 * 20830 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 20831 * 20832 * ## Show me an example using SCE. 20833 * 20834 * <example module="mySceApp" deps="angular-sanitize.js"> 20835 * <file name="index.html"> 20836 * <div ng-controller="AppController as myCtrl"> 20837 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 20838 * <b>User comments</b><br> 20839 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 20840 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 20841 * exploit. 20842 * <div class="well"> 20843 * <div ng-repeat="userComment in myCtrl.userComments"> 20844 * <b>{{userComment.name}}</b>: 20845 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 20846 * <br> 20847 * </div>
20848 * </div> 20849 * </div> 20850 * </file> 20851 * 20852 * <file name="script.js"> 20853 * angular.module('mySceApp', ['ngSanitize']) 20854 * .controller('AppController', ['$http', '$templateCache', '$sce', 20855 * function($http, $templateCache, $sce) { 20856 * var self = this; 20857 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 20858 * self.userComments = userComments; 20859 * }); 20860 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 20861 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 20862 * 'sanitization."">Hover over this text.</span>'); 20863 * }]); 20864 * </file> 20865 * 20866 * <file name="test_data.json"> 20867 * [ 20868 * { "name": "Alice", 20869 * "htmlComment": 20870 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 20871 * }, 20872 * { "name": "Bob", 20873 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 20874 * } 20875 * ] 20876 * </file> 20877 * 20878 * <file name="protractor.js" type="protractor"> 20879 * describe('SCE doc demo', function() { 20880 * it('should sanitize untrusted values', function() { 20881 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 20882 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 20883 * }); 20884 * 20885 * it('should NOT sanitize explicitly trusted values', function() { 20886 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 20887 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 20888 * 'sanitization."">Hover over this text.</span>'); 20889 * }); 20890 * }); 20891 * </file> 20892 * </example> 20893 * 20894 * 20895 * 20896 * ## Can I disable SCE completely? 20897 * 20898 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 20899 * for little coding overhead. It will be much harder to take an SCE disabled application and 20900 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 20901 * for cases where you have a lot of existing code that was written before SCE was introduced and 20902 * you're migrating them a module at a time. 20903 * 20904 * That said, here's how you can completely disable SCE: 20905 * 20906 * ``` 20907 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 20908 * // Completely disable SCE. For demonstration purposes only! 20909 * // Do not use in new projects. 20910 * $sceProvider.enabled(false); 20911 * }); 20912 * ``` 20913 * 20914 */ 20915/* jshint maxlen: 100 */ 20916 20917function $SceProvider() { 20918 var enabled = true; 20919 20920 /** 20921 * @ngdoc method 20922 * @name $sceProvider#enabled 20923 * @kind function 20924 * 20925 * @param {boolean=} value If provided, then enables/disables SCE. 20926 * @return {boolean} true if SCE is enabled, false otherwise. 20927 * 20928 * @description 20929 * Enables/disables SCE and returns the current value. 20930 */ 20931 this.enabled = function(value) { 20932 if (arguments.length) { 20933 enabled = !!value; 20934 } 20935 return enabled; 20936 }; 20937 20938 20939 /* Design notes on the default implementation for SCE. 20940 * 20941 * The API contract for the SCE delegate 20942 * ------------------------------------- 20943 * The SCE delegate object must provide the following 3 methods: 20944 * 20945 * - trustAs(contextEnum, value) 20946 * This method is used to tell the SCE service that the provided value is OK to use in the 20947 * contexts specified by contextEnum. It must return an object that will be accepted by 20948 * getTrusted() for a compatible contextEnum and return this value. 20949 * 20950 * - valueOf(value) 20951 * For values that were not produced by trustAs(), return them as is. For values that were 20952 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 20953 * trustAs is wrapping the given values into some type, this operation unwraps it when given 20954 * such a value. 20955 * 20956 * - getTrusted(contextEnum, value) 20957 * This function should return the a value that is safe to use in the context specified by 20958 * contextEnum or throw and exception otherwise. 20959 * 20960 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 20961 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 20962 * instance, an implementation could maintain a registry of all trusted objects by context. In 20963 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 20964 * return the same object passed in if it was found in the registry under a compatible context or 20965 * throw an exception otherwise. An implementation might only wrap values some of the time based 20966 * on some criteria. getTrusted() might return a value and not throw an exception for special 20967 * constants or objects even if not wrapped. All such implementations fulfill this contract. 20968 * 20969 * 20970 * A note on the inheritance model for SCE contexts 20971 * ------------------------------------------------ 20972 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 20973 * is purely an implementation details. 20974 * 20975 * The contract is simply this: 20976 * 20977 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 20978 * will also succeed. 20979 * 20980 * Inheritance happens to capture this in a natural way. In some future, we
20981 * may not use inheritance anymore. That is OK because no code outside of 20982 * sce.js and sceSpecs.js would need to be aware of this detail. 20983 */ 20984 20985 this.$get = ['$parse', '$sceDelegate', function( 20986 $parse, $sceDelegate) { 20987 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 20988 // the "expression(javascript expression)" syntax which is insecure. 20989 if (enabled && msie < 8) { 20990 throw $sceMinErr('iequirks', 20991 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 20992 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 20993 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 20994 } 20995 20996 var sce = shallowCopy(SCE_CONTEXTS); 20997 20998 /** 20999 * @ngdoc method 21000 * @name $sce#isEnabled 21001 * @kind function 21002 * 21003 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 21004 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 21005 * 21006 * @description 21007 * Returns a boolean indicating if SCE is enabled. 21008 */ 21009 sce.isEnabled = function() { 21010 return enabled; 21011 }; 21012 sce.trustAs = $sceDelegate.trustAs; 21013 sce.getTrusted = $sceDelegate.getTrusted; 21014 sce.valueOf = $sceDelegate.valueOf; 21015 21016 if (!enabled) { 21017 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 21018 sce.valueOf = identity; 21019 } 21020 21021 /** 21022 * @ngdoc method 21023 * @name $sce#parseAs 21024 * 21025 * @description 21026 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 21027 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 21028 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 21029 * *result*)} 21030 * 21031 * @param {string} type The kind of SCE context in which this result will be used. 21032 * @param {string} expression String expression to compile. 21033 * @returns {function(context, locals)} a function which represents the compiled expression: 21034 * 21035 * * `context` â `{object}` â an object against which any expressions embedded in the strings 21036 * are evaluated against (typically a scope object). 21037 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 21038 * `context`. 21039 */ 21040 sce.parseAs = function sceParseAs(type, expr) { 21041 var parsed = $parse(expr); 21042 if (parsed.literal && parsed.constant) { 21043 return parsed; 21044 } else { 21045 return $parse(expr, function(value) { 21046 return sce.getTrusted(type, value); 21047 }); 21048 } 21049 }; 21050 21051 /** 21052 * @ngdoc method 21053 * @name $sce#trustAs 21054 * 21055 * @description 21056 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 21057 * returns an object that is trusted by angular for use in specified strict contextual 21058 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 21059 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 21060 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 21061 * escaping. 21062 * 21063 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 21064 * resourceUrl, html, js and css. 21065 * @param {*} value The value that that should be considered trusted/safe. 21066 * @returns {*} A value that can be used to stand in for the provided `value` in places 21067 * where Angular expects a $sce.trustAs() return value. 21068 */ 21069 21070 /** 21071 * @ngdoc method 21072 * @name $sce#trustAsHtml 21073 * 21074 * @description 21075 * Shorthand method. `$sce.trustAsHtml(value)` â 21076 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 21077 * 21078 * @param {*} value The value to trustAs. 21079 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 21080 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 21081 * only accept expressions that are either literal constants or are the 21082 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 21083 */ 21084 21085 /** 21086 * @ngdoc method 21087 * @name $sce#trustAsUrl 21088 *
21089 * @description 21090 * Shorthand method. `$sce.trustAsUrl(value)` â 21091 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 21092 * 21093 * @param {*} value The value to trustAs. 21094 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 21095 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 21096 * only accept expressions that are either literal constants or are the 21097 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 21098 */ 21099 21100 /** 21101 * @ngdoc method 21102 * @name $sce#trustAsResourceUrl 21103 * 21104 * @description 21105 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 21106 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 21107 * 21108 * @param {*} value The value to trustAs. 21109 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 21110 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 21111 * only accept expressions that are either literal constants or are the return 21112 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 21113 */ 21114 21115 /** 21116 * @ngdoc method 21117 * @name $sce#trustAsJs 21118 * 21119 * @description 21120 * Shorthand method. `$sce.trustAsJs(value)` â 21121 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 21122 * 21123 * @param {*} value The value to trustAs. 21124 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 21125 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 21126 * only accept expressions that are either literal constants or are the 21127 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 21128 */ 21129 21130 /** 21131 * @ngdoc method 21132 * @name $sce#getTrusted 21133 * 21134 * @description 21135 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 21136 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 21137 * originally supplied value if the queried context type is a supertype of the created type. 21138 * If this condition isn't satisfied, throws an exception. 21139 * 21140 * @param {string} type The kind of context in which this value is to be used. 21141 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 21142 * call. 21143 * @returns {*} The value the was originally provided to 21144 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 21145 * Otherwise, throws an exception. 21146 */ 21147 21148 /** 21149 * @ngdoc method 21150 * @name $sce#getTrustedHtml 21151 * 21152 * @description 21153 * Shorthand method. `$sce.getTrustedHtml(value)` â 21154 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 21155 * 21156 * @param {*} value The value to pass to `$sce.getTrusted`. 21157 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 21158 */ 21159 21160 /** 21161 * @ngdoc method 21162 * @name $sce#getTrustedCss 21163 * 21164 * @description 21165 * Shorthand method. `$sce.getTrustedCss(value)` â 21166 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 21167 * 21168 * @param {*} value The value to pass to `$sce.getTrusted`. 21169 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 21170 */ 21171 21172 /** 21173 * @ngdoc method 21174 * @name $sce#getTrustedUrl 21175 * 21176 * @description 21177 * Shorthand method. `$sce.getTrustedUrl(value)` â 21178 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 21179 * 21180 * @param {*} value The value to pass to `$sce.getTrusted`. 21181 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 21182 */ 21183 21184 /** 21185 * @ngdoc method 21186 * @name $sce#getTrustedResourceUrl 21187 * 21188 * @description 21189 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 21190 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 21191 * 21192 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 21193 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 21194 */ 21195 21196 /** 21197 * @ngdoc method 21198 * @name $sce#getTrustedJs 21199 *
21200 * @description 21201 * Shorthand method. `$sce.getTrustedJs(value)` â 21202 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 21203 * 21204 * @param {*} value The value to pass to `$sce.getTrusted`. 21205 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 21206 */ 21207 21208 /** 21209 * @ngdoc method 21210 * @name $sce#parseAsHtml 21211 * 21212 * @description 21213 * Shorthand method. `$sce.parseAsHtml(expression string)` â 21214 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 21215 * 21216 * @param {string} expression String expression to compile. 21217 * @returns {function(context, locals)} a function which represents the compiled expression: 21218 * 21219 * * `context` â `{object}` â an object against which any expressions embedded in the strings 21220 * are evaluated against (typically a scope object). 21221 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 21222 * `context`. 21223 */ 21224 21225 /** 21226 * @ngdoc method 21227 * @name $sce#parseAsCss 21228 * 21229 * @description 21230 * Shorthand method. `$sce.parseAsCss(value)` â 21231 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 21232 * 21233 * @param {string} expression String expression to compile. 21234 * @returns {function(context, locals)} a function which represents the compiled expression: 21235 * 21236 * * `context` â `{object}` â an object against which any expressions embedded in the strings 21237 * are evaluated against (typically a scope object). 21238 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 21239 * `context`. 21240 */ 21241 21242 /** 21243 * @ngdoc method 21244 * @name $sce#parseAsUrl 21245 * 21246 * @description 21247 * Shorthand method. `$sce.parseAsUrl(value)` â 21248 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 21249 * 21250 * @param {string} expression String expression to compile. 21251 * @returns {function(context, locals)} a function which represents the compiled expression: 21252 * 21253 * * `context` â `{object}` â an object against which any expressions embedded in the strings 21254 * are evaluated against (typically a scope object). 21255 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 21256 * `context`. 21257 */ 21258 21259 /** 21260 * @ngdoc method 21261 * @name $sce#parseAsResourceUrl 21262 * 21263 * @description 21264 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 21265 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 21266 * 21267 * @param {string} expression String expression to compile. 21268 * @returns {function(context, locals)} a function which represents the compiled expression: 21269 * 21270 * * `context` â `{object}` â an object against which any expressions embedded in the strings 21271 * are evaluated against (typically a scope object). 21272 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 21273 * `context`. 21274 */ 21275 21276 /** 21277 * @ngdoc method 21278 * @name $sce#parseAsJs 21279 * 21280 * @description 21281 * Shorthand method. `$sce.parseAsJs(value)` â 21282 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 21283 * 21284 * @param {string} expression String expression to compile. 21285 * @returns {function(context, locals)} a function which represents the compiled expression: 21286 * 21287 * * `context` â `{object}` â an object against which any expressions embedded in the strings 21288 * are evaluated against (typically a scope object). 21289 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 21290 * `context`. 21291 */ 21292 21293 // Shorthand delegations. 21294 var parse = sce.parseAs, 21295 getTrusted = sce.getTrusted, 21296 trustAs = sce.trustAs; 21297 21298 forEach(SCE_CONTEXTS, function(enumValue, name) { 21299 var lName = lowercase(name); 21300 sce[camelCase("parse_as_" + lName)] = function(expr) { 21301 return parse(enumValue, expr); 21302 }; 21303 sce[camelCase("get_trusted_" + lName)] = function(value) { 21304 return getTrusted(enumValue, value); 21305 }; 21306 sce[camelCase("trust_as_" + lName)] = function(value) { 21307 return trustAs(enumValue, value); 21308 }; 21309 }); 21310 21311 return sce; 21312 }]; 21313} 21314 21315/** 21316 * !!! This is an undocumented "private" service !!! 21317 * 21318 * @name $sniffer 21319 * @requires $window 21320 * @requires $document 21321 * 21322 * @property {boolean} history Does the browser support html5 history api ? 21323 * @property {boolean} transitions Does the browser support CSS transition events ? 21324 * @property {boolean} animations Does the browser support CSS animation events ? 21325 * 21326 * @description 21327 * This is very simple implementation of testing browser's features. 21328 */ 21329function $SnifferProvider() { 21330 this.$get = ['$window', '$document', function($window, $document) { 21331 var eventSupport = {}, 21332 // Chrome Packaged Apps are not allowed to access `history.pushState`. They can be detected by 21333 // the presence of `chrome.app.runtime` (see https://developer.chrome.com/apps/api_index) 21334 isChromePackagedApp = $window.chrome && $window.chrome.app && $window.chrome.app.runtime, 21335 hasHistoryPushState = !isChromePackagedApp && $window.history && $window.history.pushState, 21336 android = 21337 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 21338 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 21339 document = $document[0] || {}, 21340 vendorPrefix, 21341 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 21342 bodyStyle = document.body && document.body.style, 21343 transitions = false, 21344 animations = false, 21345 match; 21346 21347 if (bodyStyle) { 21348 for (var prop in bodyStyle) {
21349 if (match = vendorRegex.exec(prop)) { 21350 vendorPrefix = match[0]; 21351 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 21352 break; 21353 } 21354 } 21355 21356 if (!vendorPrefix) { 21357 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 21358 } 21359 21360 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 21361 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 21362 21363 if (android && (!transitions || !animations)) { 21364 transitions = isString(bodyStyle.webkitTransition); 21365 animations = isString(bodyStyle.webkitAnimation); 21366 } 21367 } 21368 21369 21370 return { 21371 // Android has history.pushState, but it does not update location correctly 21372 // so let's not use the history API at all. 21373 // http://code.google.com/p/android/issues/detail?id=17471 21374 // https://github.com/angular/angular.js/issues/904 21375 21376 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 21377 // so let's not use the history API also 21378 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 21379 // jshint -W018 21380 history: !!(hasHistoryPushState && !(android < 4) && !boxee), 21381 // jshint +W018 21382 hasEvent: function(event) { 21383 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 21384 // it. In particular the event is not fired when backspace or delete key are pressed or 21385 // when cut operation is performed. 21386 // IE10+ implements 'input' event but it erroneously fires under various situations, 21387 // e.g. when placeholder changes, or a form is focused. 21388 if (event === 'input' && msie <= 11) return false; 21389 21390 if (isUndefined(eventSupport[event])) { 21391 var divElm = document.createElement('div'); 21392 eventSupport[event] = 'on' + event in divElm; 21393 } 21394 21395 return eventSupport[event]; 21396 }, 21397 csp: csp(), 21398 vendorPrefix: vendorPrefix, 21399 transitions: transitions, 21400 animations: animations, 21401 android: android 21402 }; 21403 }]; 21404} 21405 21406var $templateRequestMinErr = minErr('$compile'); 21407 21408/** 21409 * @ngdoc provider 21410 * @name $templateRequestProvider 21411 * @description 21412 * Used to configure the options passed to the {@link $http} service when making a template request. 21413 * 21414 * For example, it can be used for specifying the "Accept" header that is sent to the server, when 21415 * requesting a template. 21416 */ 21417function $TemplateRequestProvider() { 21418 21419 var httpOptions; 21420 21421 /** 21422 * @ngdoc method 21423 * @name $templateRequestProvider#httpOptions 21424 * @description 21425 * The options to be passed to the {@link $http} service when making the request. 21426 * You can use this to override options such as the "Accept" header for template requests. 21427 * 21428 * The {@link $templateRequest} will set the `cache` and the `transformResponse` properties of the 21429 * options if not overridden here. 21430 * 21431 * @param {string=} value new value for the {@link $http} options. 21432 * @returns {string|self} Returns the {@link $http} options when used as getter and self if used as setter. 21433 */ 21434 this.httpOptions = function(val) { 21435 if (val) { 21436 httpOptions = val; 21437 return this; 21438 } 21439 return httpOptions; 21440 }; 21441 21442 /** 21443 * @ngdoc service 21444 * @name $templateRequest 21445 * 21446 * @description 21447 * The `$templateRequest` service runs security checks then downloads the provided template using 21448 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 21449 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 21450 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 21451 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 21452 * when `tpl` is of type string and `$templateCache` has the matching entry. 21453 * 21454 * If you want to pass custom options to the `$http` service, such as setting the Accept header you 21455 * can configure this via {@link $templateRequestProvider#httpOptions}. 21456 * 21457 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 21458 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 21459 * 21460 * @return {Promise} a promise for the HTTP response data of the given URL. 21461 * 21462 * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
21463 */ 21464 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { 21465 21466 function handleRequestFn(tpl, ignoreRequestError) { 21467 handleRequestFn.totalPendingRequests++; 21468 21469 // We consider the template cache holds only trusted templates, so 21470 // there's no need to go through whitelisting again for keys that already 21471 // are included in there. This also makes Angular accept any script 21472 // directive, no matter its name. However, we still need to unwrap trusted 21473 // types. 21474 if (!isString(tpl) || !$templateCache.get(tpl)) { 21475 tpl = $sce.getTrustedResourceUrl(tpl); 21476 } 21477 21478 var transformResponse = $http.defaults && $http.defaults.transformResponse; 21479 21480 if (isArray(transformResponse)) { 21481 transformResponse = transformResponse.filter(function(transformer) { 21482 return transformer !== defaultHttpResponseTransform; 21483 }); 21484 } else if (transformResponse === defaultHttpResponseTransform) { 21485 transformResponse = null; 21486 } 21487 21488 return $http.get(tpl, extend({ 21489 cache: $templateCache, 21490 transformResponse: transformResponse 21491 }, httpOptions)) 21492 ['finally'](function() { 21493 handleRequestFn.totalPendingRequests--; 21494 }) 21495 .then(function(response) { 21496 $templateCache.put(tpl, response.data); 21497 return response.data; 21498 }, handleError); 21499 21500 function handleError(resp) { 21501 if (!ignoreRequestError) { 21502 throw $templateRequestMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 21503 tpl, resp.status, resp.statusText); 21504 } 21505 return $q.reject(resp); 21506 } 21507 } 21508 21509 handleRequestFn.totalPendingRequests = 0; 21510 21511 return handleRequestFn; 21512 }]; 21513} 21514 21515function $$TestabilityProvider() { 21516 this.$get = ['$rootScope', '$browser', '$location', 21517 function($rootScope, $browser, $location) { 21518 21519 /** 21520 * @name $testability 21521 * 21522 * @description 21523 * The private $$testability service provides a collection of methods for use when debugging 21524 * or by automated test and debugging tools. 21525 */ 21526 var testability = {}; 21527 21528 /** 21529 * @name $$testability#findBindings 21530 * 21531 * @description 21532 * Returns an array of elements that are bound (via ng-bind or {{}}) 21533 * to expressions matching the input. 21534 * 21535 * @param {Element} element The element root to search from. 21536 * @param {string} expression The binding expression to match. 21537 * @param {boolean} opt_exactMatch If true, only returns exact matches 21538 * for the expression. Filters and whitespace are ignored. 21539 */ 21540 testability.findBindings = function(element, expression, opt_exactMatch) { 21541 var bindings = element.getElementsByClassName('ng-binding'); 21542 var matches = []; 21543 forEach(bindings, function(binding) { 21544 var dataBinding = angular.element(binding).data('$binding'); 21545 if (dataBinding) { 21546 forEach(dataBinding, function(bindingName) { 21547 if (opt_exactMatch) { 21548 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 21549 if (matcher.test(bindingName)) { 21550 matches.push(binding); 21551 } 21552 } else { 21553 if (bindingName.indexOf(expression) != -1) { 21554 matches.push(binding); 21555 } 21556 } 21557 }); 21558 } 21559 }); 21560 return matches; 21561 }; 21562 21563 /** 21564 * @name $$testability#findModels 21565 * 21566 * @description 21567 * Returns an array of elements that are two-way found via ng-model to 21568 * expressions matching the input. 21569 * 21570 * @param {Element} element The element root to search from. 21571 * @param {string} expression The model expression to match. 21572 * @param {boolean} opt_exactMatch If true, only returns exact matches 21573 * for the expression. 21574 */ 21575 testability.findModels = function(element, expression, opt_exactMatch) { 21576 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 21577 for (var p = 0; p < prefixes.length; ++p) { 21578 var attributeEquals = opt_exactMatch ? '=' : '*='; 21579 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 21580 var elements = element.querySelectorAll(selector); 21581 if (elements.length) { 21582 return elements; 21583 } 21584 } 21585 }; 21586 21587 /** 21588 * @name $$testability#getLocation 21589 *
21590 * @description 21591 * Shortcut for getting the location in a browser agnostic way. Returns 21592 * the path, search, and hash. (e.g. /path?a=b#hash) 21593 */ 21594 testability.getLocation = function() { 21595 return $location.url(); 21596 }; 21597 21598 /** 21599 * @name $$testability#setLocation 21600 * 21601 * @description 21602 * Shortcut for navigating to a location without doing a full page reload. 21603 * 21604 * @param {string} url The location url (path, search and hash, 21605 * e.g. /path?a=b#hash) to go to. 21606 */ 21607 testability.setLocation = function(url) { 21608 if (url !== $location.url()) { 21609 $location.url(url); 21610 $rootScope.$digest(); 21611 } 21612 }; 21613 21614 /** 21615 * @name $$testability#whenStable 21616 * 21617 * @description 21618 * Calls the callback when $timeout and $http requests are completed. 21619 * 21620 * @param {function} callback 21621 */ 21622 testability.whenStable = function(callback) { 21623 $browser.notifyWhenNoOutstandingRequests(callback); 21624 }; 21625 21626 return testability; 21627 }]; 21628} 21629 21630function $TimeoutProvider() { 21631 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 21632 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 21633 21634 var deferreds = {}; 21635 21636 21637 /** 21638 * @ngdoc service 21639 * @name $timeout 21640 * 21641 * @description 21642 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 21643 * block and delegates any exceptions to 21644 * {@link ng.$exceptionHandler $exceptionHandler} service. 21645 * 21646 * The return value of calling `$timeout` is a promise, which will be resolved when 21647 * the delay has passed and the timeout function, if provided, is executed. 21648 * 21649 * To cancel a timeout request, call `$timeout.cancel(promise)`. 21650 * 21651 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 21652 * synchronously flush the queue of deferred functions. 21653 * 21654 * If you only want a promise that will be resolved after some specified delay 21655 * then you can call `$timeout` without the `fn` function. 21656 * 21657 * @param {function()=} fn A function, whose execution should be delayed. 21658 * @param {number=} [delay=0] Delay in milliseconds. 21659 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 21660 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 21661 * @param {...*=} Pass additional parameters to the executed function. 21662 * @returns {Promise} Promise that will be resolved when the timeout is reached. The promise 21663 * will be resolved with the return value of the `fn` function. 21664 * 21665 */ 21666 function timeout(fn, delay, invokeApply) { 21667 if (!isFunction(fn)) { 21668 invokeApply = delay; 21669 delay = fn; 21670 fn = noop; 21671 } 21672 21673 var args = sliceArgs(arguments, 3), 21674 skipApply = (isDefined(invokeApply) && !invokeApply), 21675 deferred = (skipApply ? $$q : $q).defer(), 21676 promise = deferred.promise, 21677 timeoutId; 21678 21679 timeoutId = $browser.defer(function() { 21680 try { 21681 deferred.resolve(fn.apply(null, args)); 21682 } catch (e) { 21683 deferred.reject(e); 21684 $exceptionHandler(e); 21685 } 21686 finally { 21687 delete deferreds[promise.$$timeoutId]; 21688 } 21689 21690 if (!skipApply) $rootScope.$apply(); 21691 }, delay); 21692 21693 promise.$$timeoutId = timeoutId; 21694 deferreds[timeoutId] = deferred; 21695 21696 return promise; 21697 } 21698 21699 21700 /** 21701 * @ngdoc method 21702 * @name $timeout#cancel 21703 * 21704 * @description 21705 * Cancels a task associated with the `promise`. As a result of this, the promise will be 21706 * resolved with a rejection. 21707 * 21708 * @param {Promise=} promise Promise returned by the `$timeout` function. 21709 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 21710 * canceled. 21711 */ 21712 timeout.cancel = function(promise) { 21713 if (promise && promise.$$timeoutId in deferreds) { 21714 deferreds[promise.$$timeoutId].reject('canceled'); 21715 delete deferreds[promise.$$timeoutId]; 21716 return $browser.defer.cancel(promise.$$timeoutId); 21717 } 21718 return false;
vendor: 5,342 bytes, lines 21719-21840
21719 }; 21720 21721 return timeout; 21722 }]; 21723} 21724 21725// NOTE: The usage of window and document instead of $window and $document here is 21726// deliberate. This service depends on the specific behavior of anchor nodes created by the 21727// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 21728// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 21729// doesn't know about mocked locations and resolves URLs to the real document - which is 21730// exactly the behavior needed here. There is little value is mocking these out for this 21731// service. 21732var urlParsingNode = window.document.createElement("a"); 21733var originUrl = urlResolve(window.location.href); 21734 21735 21736/** 21737 * 21738 * Implementation Notes for non-IE browsers 21739 * ---------------------------------------- 21740 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 21741 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 21742 * URL will be resolved into an absolute URL in the context of the application document. 21743 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 21744 * properties are all populated to reflect the normalized URL. This approach has wide 21745 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 21746 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 21747 * 21748 * Implementation Notes for IE 21749 * --------------------------- 21750 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 21751 * browsers. However, the parsed components will not be set if the URL assigned did not specify 21752 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 21753 * work around that by performing the parsing in a 2nd step by taking a previously normalized 21754 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 21755 * properties such as protocol, hostname, port, etc. 21756 * 21757 * References: 21758 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 21759 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 21760 * http://url.spec.whatwg.org/#urlutils 21761 * https://github.com/angular/angular.js/pull/2902 21762 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 21763 * 21764 * @kind function 21765 * @param {string} url The URL to be parsed. 21766 * @description Normalizes and parses a URL. 21767 * @returns {object} Returns the normalized URL as a dictionary. 21768 * 21769 * | member name | Description | 21770 * |---------------|----------------| 21771 * | href | A normalized version of the provided URL if it was not an absolute URL | 21772 * | protocol | The protocol including the trailing colon | 21773 * | host | The host and port (if the port is non-default) of the normalizedUrl | 21774 * | search | The search params, minus the question mark | 21775 * | hash | The hash string, minus the hash symbol 21776 * | hostname | The hostname 21777 * | port | The port, without ":" 21778 * | pathname | The pathname, beginning with "/" 21779 * 21780 */ 21781function urlResolve(url) { 21782 var href = url; 21783 21784 if (msie) { 21785 // Normalize before parse. Refer Implementation Notes on why this is 21786 // done in two steps on IE. 21787 urlParsingNode.setAttribute("href", href); 21788 href = urlParsingNode.href; 21789 } 21790 21791 urlParsingNode.setAttribute('href', href); 21792 21793 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 21794 return { 21795 href: urlParsingNode.href, 21796 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 21797 host: urlParsingNode.host, 21798 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 21799 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 21800 hostname: urlParsingNode.hostname, 21801 port: urlParsingNode.port, 21802 pathname: (urlParsingNode.pathname.charAt(0) === '/') 21803 ? urlParsingNode.pathname 21804 : '/' + urlParsingNode.pathname 21805 }; 21806} 21807 21808/** 21809 * Parse a request URL and determine whether this is a same-origin request as the application document. 21810 * 21811 * @param {string|object} requestUrl The url of the request as a string that will be resolved 21812 * or a parsed URL object. 21813 * @returns {boolean} Whether the request is for the same origin as the application document. 21814 */ 21815function urlIsSameOrigin(requestUrl) { 21816 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 21817 return (parsed.protocol === originUrl.protocol && 21818 parsed.host === originUrl.host); 21819} 21820 21821/** 21822 * @ngdoc service 21823 * @name $window 21824 * 21825 * @description 21826 * A reference to the browser's `window` object. While `window` 21827 * is globally available in JavaScript, it causes testability problems, because 21828 * it is a global variable. In angular we always refer to it through the 21829 * `$window` service, so it may be overridden, removed or mocked for testing. 21830 * 21831 * Expressions, like the one defined for the `ngClick` directive in the example 21832 * below, are evaluated with respect to the current scope. Therefore, there is 21833 * no risk of inadvertently coding in a dependency on a global value in such an 21834 * expression. 21835 * 21836 * @example 21837 <example module="windowExample"> 21838 <file name="index.html"> 21839 <script> 21840 angular.module('windowExample', [])
21841 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 21842 $scope.greeting = 'Hello, World!'; 21843 $scope.doGreeting = function(greeting) { 21844 $window.alert(greeting); 21845 }; 21846 }]); 21847 </script> 21848 <div ng-controller="ExampleController"> 21849 <input type="text" ng-model="greeting" aria-label="greeting" /> 21850 <button ng-click="doGreeting(greeting)">ALERT</button> 21851 </div> 21852 </file> 21853 <file name="protractor.js" type="protractor"> 21854 it('should display the greeting in the input box', function() { 21855 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 21856 // If we click the button it will block the test runner 21857 // element(':button').click(); 21858 }); 21859 </file> 21860 </example> 21861 */ 21862function $WindowProvider() { 21863 this.$get = valueFn(window); 21864} 21865 21866/** 21867 * @name $$cookieReader 21868 * @requires $document 21869 * 21870 * @description 21871 * This is a private service for reading cookies used by $http and ngCookies 21872 * 21873 * @return {Object} a key/value map of the current cookies 21874 */ 21875function $$CookieReader($document) { 21876 var rawDocument = $document[0] || {}; 21877 var lastCookies = {}; 21878 var lastCookieString = ''; 21879 21880 function safeDecodeURIComponent(str) { 21881 try { 21882 return decodeURIComponent(str); 21883 } catch (e) { 21884 return str; 21885 } 21886 } 21887 21888 return function() { 21889 var cookieArray, cookie, i, index, name; 21890 var currentCookieString = rawDocument.cookie || ''; 21891 21892 if (currentCookieString !== lastCookieString) { 21893 lastCookieString = currentCookieString; 21894 cookieArray = lastCookieString.split('; '); 21895 lastCookies = {}; 21896 21897 for (i = 0; i < cookieArray.length; i++) { 21898 cookie = cookieArray[i]; 21899 index = cookie.indexOf('='); 21900 if (index > 0) { //ignore nameless cookies 21901 name = safeDecodeURIComponent(cookie.substring(0, index)); 21902 // the first value that is seen for a cookie is the most 21903 // specific one. values for the same cookie name that 21904 // follow are for less specific paths. 21905 if (isUndefined(lastCookies[name])) { 21906 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 21907 } 21908 } 21909 } 21910 } 21911 return lastCookies; 21912 }; 21913} 21914 21915$$CookieReader.$inject = ['$document']; 21916 21917function $$CookieReaderProvider() { 21918 this.$get = $$CookieReader; 21919} 21920 21921/* global currencyFilter: true, 21922 dateFilter: true, 21923 filterFilter: true, 21924 jsonFilter: true, 21925 limitToFilter: true, 21926 lowercaseFilter: true, 21927 numberFilter: true, 21928 orderByFilter: true, 21929 uppercaseFilter: true, 21930 */ 21931 21932/** 21933 * @ngdoc provider 21934 * @name $filterProvider 21935 * @description 21936 * 21937 * Filters are just functions which transform input to an output. However filters need to be 21938 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 21939 * annotated with dependencies and is responsible for creating a filter function. 21940 * 21941 * <div class="alert alert-warning"> 21942 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 21943 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 21944 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 21945 * (`myapp_subsection_filterx`). 21946 * </div> 21947 * 21948 * ```js 21949 * // Filter registration 21950 * function MyModule($provide, $filterProvider) { 21951 * // create a service to demonstrate injection (not always needed) 21952 * $provide.value('greet', function(name){ 21953 * return 'Hello ' + name + '!'; 21954 * }); 21955 * 21956 * // register a filter factory which uses the 21957 * // greet service to demonstrate DI. 21958 * $filterProvider.register('greet', function(greet){ 21959 * // return the filter function which uses the greet service 21960 * // to generate salutation 21961 * return function(text) { 21962 * // filters need to be forgiving so check input validity 21963 * return text && greet(text) || text; 21964 * }; 21965 * }); 21966 * } 21967 * ``` 21968 * 21969 * The filter function is registered with the `$injector` under the filter name suffix with 21970 * `Filter`. 21971 * 21972 * ```js 21973 * it('should be the same instance', inject( 21974 * function($filterProvider) { 21975 * $filterProvider.register('reverse', function(){ 21976 * return ...; 21977 * }); 21978 * }, 21979 * function($filter, reverseFilter) { 21980 * expect($filter('reverse')).toBe(reverseFilter); 21981 * }); 21982 * ``` 21983 * 21984 * 21985 * For more information about how angular filters work, and how to create your own filters, see 21986 * {@link guide/filter Filters} in the Angular Developer Guide. 21987 */ 21988 21989/** 21990 * @ngdoc service 21991 * @name $filter 21992 * @kind function 21993 * @description 21994 * Filters are used for formatting data displayed to the user. 21995 * 21996 * The general syntax in templates is as follows: 21997 * 21998 * {{ expression [| filter_name[:parameter_value] ... ] }} 21999 * 22000 * @param {String} name Name of the filter function to retrieve 22001 * @return {Function} the filter function 22002 * @example 22003 <example name="$filter" module="filterExample"> 22004 <file name="index.html"> 22005 <div ng-controller="MainCtrl"> 22006 <h3>{{ originalText }}</h3> 22007 <h3>{{ filteredText }}</h3> 22008 </div> 22009 </file> 22010 22011 <file name="script.js"> 22012 angular.module('filterExample', []) 22013 .controller('MainCtrl', function($scope, $filter) { 22014 $scope.originalText = 'hello'; 22015 $scope.filteredText = $filter('uppercase')($scope.originalText); 22016 }); 22017 </file> 22018 </example> 22019 */ 22020$FilterProvider.$inject = ['$provide']; 22021function $FilterProvider($provide) { 22022 var suffix = 'Filter'; 22023 22024 /** 22025 * @ngdoc method 22026 * @name $filterProvider#register 22027 * @param {string|Object} name Name of the filter function, or an object map of filters where 22028 * the keys are the filter names and the values are the filter factories. 22029 * 22030 * <div class="alert alert-warning"> 22031 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 22032 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 22033 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 22034 * (`myapp_subsection_filterx`). 22035 * </div> 22036 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 22037 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 22038 * of the registered filter instances. 22039 */ 22040 function register(name, factory) { 22041 if (isObject(name)) { 22042 var filters = {};
22043 forEach(name, function(filter, key) { 22044 filters[key] = register(key, filter); 22045 }); 22046 return filters; 22047 } else { 22048 return $provide.factory(name + suffix, factory); 22049 } 22050 } 22051 this.register = register; 22052 22053 this.$get = ['$injector', function($injector) { 22054 return function(name) { 22055 return $injector.get(name + suffix); 22056 }; 22057 }]; 22058 22059 //////////////////////////////////////// 22060 22061 /* global 22062 currencyFilter: false, 22063 dateFilter: false, 22064 filterFilter: false, 22065 jsonFilter: false, 22066 limitToFilter: false, 22067 lowercaseFilter: false, 22068 numberFilter: false, 22069 orderByFilter: false, 22070 uppercaseFilter: false, 22071 */ 22072 22073 register('currency', currencyFilter); 22074 register('date', dateFilter); 22075 register('filter', filterFilter); 22076 register('json', jsonFilter); 22077 register('limitTo', limitToFilter); 22078 register('lowercase', lowercaseFilter); 22079 register('number', numberFilter); 22080 register('orderBy', orderByFilter); 22081 register('uppercase', uppercaseFilter); 22082} 22083 22084/** 22085 * @ngdoc filter 22086 * @name filter 22087 * @kind function 22088 * 22089 * @description 22090 * Selects a subset of items from `array` and returns it as a new array. 22091 * 22092 * @param {Array} array The source array. 22093 * @param {string|Object|function()} expression The predicate to be used for selecting items from 22094 * `array`. 22095 * 22096 * Can be one of: 22097 * 22098 * - `string`: The string is used for matching against the contents of the `array`. All strings or 22099 * objects with string properties in `array` that match this string will be returned. This also 22100 * applies to nested object properties. 22101 * The predicate can be negated by prefixing the string with `!`. 22102 * 22103 * - `Object`: A pattern object can be used to filter specific properties on objects contained 22104 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 22105 * which have property `name` containing "M" and property `phone` containing "1". A special 22106 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 22107 * property of the object or its nested object properties. That's equivalent to the simple 22108 * substring match with a `string` as described above. The predicate can be negated by prefixing 22109 * the string with `!`. 22110 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 22111 * not containing "M". 22112 * 22113 * Note that a named property will match properties on the same level only, while the special 22114 * `$` property will match properties on the same level or deeper. E.g. an array item like 22115 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 22116 * **will** be matched by `{$: 'John'}`. 22117 * 22118 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 22119 * The function is called for each element of the array, with the element, its index, and 22120 * the entire array itself as arguments. 22121 * 22122 * The final result is an array of those elements that the predicate returned true for. 22123 * 22124 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 22125 * determining if the expected value (from the filter expression) and actual value (from 22126 * the object in the array) should be considered a match. 22127 * 22128 * Can be one of: 22129 * 22130 * - `function(actual, expected)`: 22131 * The function will be given the object value and the predicate value to compare and 22132 * should return true if both values should be considered equal. 22133 * 22134 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 22135 * This is essentially strict comparison of expected and actual. 22136 * 22137 * - `false|undefined`: A short hand for a function which will look for a substring match in case 22138 * insensitive way. 22139 * 22140 * Primitive values are converted to strings. Objects are not compared against primitives, 22141 * unless they have a custom `toString` method (e.g. `Date` objects). 22142 * 22143 * @example 22144 <example> 22145 <file name="index.html"> 22146 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 22147 {name:'Mary', phone:'800-BIG-MARY'}, 22148 {name:'Mike', phone:'555-4321'}, 22149 {name:'Adam', phone:'555-5678'}, 22150 {name:'Julie', phone:'555-8765'}, 22151 {name:'Juliette', phone:'555-5678'}]"></div> 22152 22153 <label>Search: <input ng-model="searchText"></label> 22154 <table id="searchTextResults"> 22155 <tr><th>
22155Name</th><th>Phone</th></tr> 22156 <tr ng-repeat="friend in friends | filter:searchText"> 22157 <td>{{friend.name}}</td> 22158 <td>{{friend.phone}}</td> 22159 </tr> 22160 </table> 22161 <hr> 22162 <label>Any: <input ng-model="search.$"></label> <br> 22163 <label>Name only <input ng-model="search.name"></label><br> 22164 <label>Phone only <input ng-model="search.phone"></label><br> 22165 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 22166 <table id="searchObjResults"> 22167 <tr><th>Name</th><th>Phone</th></tr> 22168 <tr ng-repeat="friendObj in friends | filter:search:strict"> 22169 <td>{{friendObj.name}}</td> 22170 <td>{{friendObj.phone}}</td> 22171 </tr> 22172 </table> 22173 </file> 22174 <file name="protractor.js" type="protractor"> 22175 var expectFriendNames = function(expectedNames, key) { 22176 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 22177 arr.forEach(function(wd, i) { 22178 expect(wd.getText()).toMatch(expectedNames[i]); 22179 }); 22180 }); 22181 }; 22182 22183 it('should search across all fields when filtering with a string', function() { 22184 var searchText = element(by.model('searchText')); 22185 searchText.clear(); 22186 searchText.sendKeys('m'); 22187 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 22188 22189 searchText.clear(); 22190 searchText.sendKeys('76'); 22191 expectFriendNames(['John', 'Julie'], 'friend'); 22192 }); 22193 22194 it('should search in specific fields when filtering with a predicate object', function() { 22195 var searchAny = element(by.model('search.$')); 22196 searchAny.clear(); 22197 searchAny.sendKeys('i'); 22198 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 22199 }); 22200 it('should use a equal comparison when comparator is true', function() { 22201 var searchName = element(by.model('search.name')); 22202 var strict = element(by.model('strict')); 22203 searchName.clear(); 22204 searchName.sendKeys('Julie'); 22205 strict.click(); 22206 expectFriendNames(['Julie'], 'friendObj'); 22207 }); 22208 </file> 22209 </example> 22210 */ 22211function filterFilter() { 22212 return function(array, expression, comparator) { 22213 if (!isArrayLike(array)) { 22214 if (array == null) { 22215 return array; 22216 } else { 22217 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 22218 } 22219 } 22220 22221 var expressionType = getTypeForFilter(expression); 22222 var predicateFn; 22223 var matchAgainstAnyProp; 22224 22225 switch (expressionType) { 22226 case 'function': 22227 predicateFn = expression; 22228 break; 22229 case 'boolean': 22230 case 'null': 22231 case 'number': 22232 case 'string': 22233 matchAgainstAnyProp = true; 22234 //jshint -W086 22235 case 'object': 22236 //jshint +W086 22237 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 22238 break; 22239 default: 22240 return array; 22241 } 22242 22243 return Array.prototype.filter.call(array, predicateFn); 22244 }; 22245} 22246 22247// Helper functions for `filterFilter` 22248function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 22249 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 22250 var predicateFn; 22251 22252 if (comparator === true) { 22253 comparator = equals; 22254 } else if (!isFunction(comparator)) { 22255 comparator = function(actual, expected) { 22256 if (isUndefined(actual)) { 22257 // No substring matching against `undefined` 22258 return false; 22259 } 22260 if ((actual === null) || (expected === null)) { 22261 // No substring matching against `null`; only match against `null` 22262 return actual === expected; 22263 } 22264 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 22265 // Should not compare primitives against objects, unless they have custom `toString` method 22266 return false; 22267 } 22268 22269 actual = lowercase('' + actual); 22270 expected = lowercase('' + expected); 22271 return actual.indexOf(expected) !== -1; 22272 }; 22273 } 22274 22275 predicateFn = function(item) { 22276 if (shouldMatchPrimitives && !isObject(item)) { 22277 return deepCompare(item, expression.$, comparator, false); 22278 } 22279 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 22280 }; 22281 22282 return predicateFn; 22283} 22284 22285function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 22286 var actualType = getTypeForFilter(actual); 22287 var expectedType = getTypeForFilter(expected); 22288 22289 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 22290 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 22291 } else if (isArray(actual)) { 22292 // In case `actual` is an array, consider it a match 22293 // if ANY of it's items matches `expected` 22294 return actual.some(function(item) { 22295 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 22296 }); 22297 } 22298 22299 switch (actualType) { 22300 case 'object': 22301 var key; 22302 if (matchAgainstAnyProp) { 22303 for (key in actual) { 22304 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 22305 return true; 22306 } 22307 } 22308 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 22309 } else if (expectedType === 'object') { 22310 for (key in expected) { 22311 var expectedVal = expected[key]; 22312 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 22313 continue; 22314 } 22315 22316 var matchAnyProperty = key === '$'; 22317 var actualVal = matchAnyProperty ? actual : actual[key]; 22318 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 22319 return false;
22320 } 22321 } 22322 return true; 22323 } else { 22324 return comparator(actual, expected); 22325 } 22326 break; 22327 case 'function': 22328 return false; 22329 default: 22330 return comparator(actual, expected); 22331 } 22332} 22333 22334// Used for easily differentiating between `null` and actual `object` 22335function getTypeForFilter(val) { 22336 return (val === null) ? 'null' : typeof val; 22337} 22338 22339var MAX_DIGITS = 22; 22340var DECIMAL_SEP = '.'; 22341var ZERO_CHAR = '0'; 22342 22343/** 22344 * @ngdoc filter 22345 * @name currency 22346 * @kind function 22347 * 22348 * @description 22349 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 22350 * symbol for current locale is used. 22351 * 22352 * @param {number} amount Input to filter. 22353 * @param {string=} symbol Currency symbol or identifier to be displayed. 22354 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 22355 * @returns {string} Formatted number. 22356 * 22357 * 22358 * @example 22359 <example module="currencyExample"> 22360 <file name="index.html"> 22361 <script> 22362 angular.module('currencyExample', []) 22363 .controller('ExampleController', ['$scope', function($scope) { 22364 $scope.amount = 1234.56; 22365 }]); 22366 </script>
22367 <div ng-controller="ExampleController"> 22368 <input type="number" ng-model="amount" aria-label="amount"> <br> 22369 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 22370 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 22371 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 22372 </div> 22373 </file> 22374 <file name="protractor.js" type="protractor"> 22375 it('should init with 1234.56', function() { 22376 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 22377 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 22378 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 22379 }); 22380 it('should update', function() { 22381 if (browser.params.browser == 'safari') { 22382 // Safari does not understand the minus key. See 22383 // https://github.com/angular/protractor/issues/481 22384 return; 22385 } 22386 element(by.model('amount')).clear(); 22387 element(by.model('amount')).sendKeys('-1234'); 22388 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 22389 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 22390 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 22391 }); 22392 </file> 22393 </example> 22394 */ 22395currencyFilter.$inject = ['$locale']; 22396function currencyFilter($locale) { 22397 var formats = $locale.NUMBER_FORMATS; 22398 return function(amount, currencySymbol, fractionSize) { 22399 if (isUndefined(currencySymbol)) { 22400 currencySymbol = formats.CURRENCY_SYM; 22401 } 22402 22403 if (isUndefined(fractionSize)) { 22404 fractionSize = formats.PATTERNS[1].maxFrac; 22405 } 22406 22407 // if null or undefined pass it through 22408 return (amount == null) 22409 ? amount 22410 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 22411 replace(/\u00A4/g, currencySymbol); 22412 }; 22413} 22414 22415/** 22416 * @ngdoc filter 22417 * @name number 22418 * @kind function 22419 * 22420 * @description 22421 * Formats a number as text. 22422 * 22423 * If the input is null or undefined, it will just be returned. 22424 * If the input is infinite (Infinity or -Infinity), the Infinity symbol 'â' or '-â' is returned, respectively. 22425 * If the input is not a number an empty string is returned. 22426 * 22427 * 22428 * @param {number|string} number Number to format. 22429 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 22430 * If this is not provided then the fraction size is computed from the current locale's number 22431 * formatting pattern. In the case of the default locale, it will be 3. 22432 * @returns {string} Number rounded to `fractionSize` appropriately formatted based on the current 22433 * locale (e.g., in the en_US locale it will have "." as the decimal separator and 22434 * include "," group separators after each third digit). 22435 * 22436 * @example 22437 <example module="numberFilterExample
vendor: 3,034 bytes, lines 22437-22516
22437"> 22438 <file name="index.html"> 22439 <script> 22440 angular.module('numberFilterExample', []) 22441 .controller('ExampleController', ['$scope', function($scope) { 22442 $scope.val = 1234.56789; 22443 }]); 22444 </script> 22445 <div ng-controller="ExampleController"> 22446 <label>Enter number: <input ng-model='val'></label><br> 22447 Default formatting: <span id='number-default'>{{val | number}}</span><br> 22448 No fractions: <span>{{val | number:0}}</span><br> 22449 Negative number: <span>{{-val | number:4}}</span> 22450 </div> 22451 </file> 22452 <file name="protractor.js" type="protractor"> 22453 it('should format numbers', function() { 22454 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 22455 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 22456 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 22457 }); 22458 22459 it('should update', function() { 22460 element(by.model('val')).clear(); 22461 element(by.model('val')).sendKeys('3374.333'); 22462 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 22463 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 22464 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 22465 }); 22466 </file> 22467 </example> 22468 */ 22469numberFilter.$inject = ['$locale']; 22470function numberFilter($locale) { 22471 var formats = $locale.NUMBER_FORMATS; 22472 return function(number, fractionSize) { 22473 22474 // if null or undefined pass it through 22475 return (number == null) 22476 ? number 22477 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 22478 fractionSize); 22479 }; 22480} 22481 22482/** 22483 * Parse a number (as a string) into three components that can be used 22484 * for formatting the number. 22485 * 22486 * (Significant bits of this parse algorithm came from https://github.com/MikeMcl/big.js/) 22487 * 22488 * @param {string} numStr The number to parse 22489 * @return {object} An object describing this number, containing the following keys: 22490 * - d : an array of digits containing leading zeros as necessary 22491 * - i : the number of the digits in `d` that are to the left of the decimal point 22492 * - e : the exponent for numbers that would need more than `MAX_DIGITS` digits in `d` 22493 * 22494 */ 22495function parse(numStr) { 22496 var exponent = 0, digits, numberOfIntegerDigits; 22497 var i, j, zeros; 22498 22499 // Decimal point? 22500 if ((numberOfIntegerDigits = numStr.indexOf(DECIMAL_SEP)) > -1) { 22501 numStr = numStr.replace(DECIMAL_SEP, ''); 22502 } 22503 22504 // Exponential form? 22505 if ((i = numStr.search(/e/i)) > 0) { 22506 // Work out the exponent. 22507 if (numberOfIntegerDigits < 0) numberOfIntegerDigits = i; 22508 numberOfIntegerDigits += +numStr.slice(i + 1); 22509 numStr = numStr.substring(0, i); 22510 } else if (numberOfIntegerDigits < 0) { 22511 // There was no decimal point or exponent so it is an integer. 22512 numberOfIntegerDigits = numStr.length; 22513 } 22514 22515 // Count the number of leading zeros. 22516 for (i = 0; numStr.charAt(i) ==
vendor: 13,216 bytes, lines 22516-22887
22516 ZERO_CHAR; i++) {/* jshint noempty: false */} 22517 22518 if (i == (zeros = numStr.length)) { 22519 // The digits are all zero. 22520 digits = [0]; 22521 numberOfIntegerDigits = 1; 22522 } else { 22523 // Count the number of trailing zeros 22524 zeros--; 22525 while (numStr.charAt(zeros) == ZERO_CHAR) zeros--; 22526 22527 // Trailing zeros are insignificant so ignore them 22528 numberOfIntegerDigits -= i; 22529 digits = []; 22530 // Convert string to array of digits without leading/trailing zeros. 22531 for (j = 0; i <= zeros; i++, j++) { 22532 digits[j] = +numStr.charAt(i); 22533 } 22534 } 22535 22536 // If the number overflows the maximum allowed digits then use an exponent. 22537 if (numberOfIntegerDigits > MAX_DIGITS) { 22538 digits = digits.splice(0, MAX_DIGITS - 1); 22539 exponent = numberOfIntegerDigits - 1; 22540 numberOfIntegerDigits = 1; 22541 } 22542 22543 return { d: digits, e: exponent, i: numberOfIntegerDigits }; 22544} 22545 22546/** 22547 * Round the parsed number to the specified number of decimal places 22548 * This function changed the parsedNumber in-place 22549 */ 22550function roundNumber(parsedNumber, fractionSize, minFrac, maxFrac) { 22551 var digits = parsedNumber.d; 22552 var fractionLen = digits.length - parsedNumber.i; 22553 22554 // determine fractionSize if it is not specified; `+fractionSize` converts it to a number 22555 fractionSize = (isUndefined(fractionSize)) ? Math.min(Math.max(minFrac, fractionLen), maxFrac) : +fractionSize; 22556 22557 // The index of the digit to where rounding is to occur 22558 var roundAt = fractionSize + parsedNumber.i; 22559 var digit = digits[roundAt]; 22560 22561 if (roundAt > 0) { 22562 // Drop fractional digits beyond `roundAt` 22563 digits.splice(Math.max(parsedNumber.i, roundAt)); 22564 22565 // Set non-fractional digits beyond `roundAt` to 0 22566 for (var j = roundAt; j < digits.length; j++) { 22567 digits[j] = 0; 22568 } 22569 } else { 22570 // We rounded to zero so reset the parsedNumber 22571 fractionLen = Math.max(0, fractionLen); 22572 parsedNumber.i = 1; 22573 digits.length = Math.max(1, roundAt = fractionSize + 1); 22574 digits[0] = 0; 22575 for (var i = 1; i < roundAt; i++) digits[i] = 0; 22576 } 22577 22578 if (digit >= 5) { 22579 if (roundAt - 1 < 0) { 22580 for (var k = 0; k > roundAt; k--) { 22581 digits.unshift(0); 22582 parsedNumber.i++; 22583 } 22584 digits.unshift(1); 22585 parsedNumber.i++; 22586 } else { 22587 digits[roundAt - 1]++; 22588 } 22589 } 22590 22591 // Pad out with zeros to get the required fraction length 22592 for (; fractionLen < Math.max(0, fractionSize); fractionLen++) digits.push(0); 22593 22594 22595 // Do any carrying, e.g. a digit was rounded up to 10 22596 var carry = digits.reduceRight(function(carry, d, i, digits) { 22597 d = d + carry; 22598 digits[i] = d % 10; 22599 return Math.floor(d / 10); 22600 }, 0); 22601 if (carry) { 22602 digits.unshift(carry); 22603 parsedNumber.i++; 22604 } 22605} 22606 22607/** 22608 * Format a number into a string 22609 * @param {number} number The number to format 22610 * @param {{ 22611 * minFrac, // the minimum number of digits required in the fraction part of the number 22612 * maxFrac, // the maximum number of digits required in the fraction part of the number 22613 * gSize, // number of digits in each group of separated digits 22614 * lgSize, // number of digits in the last group of digits before the decimal separator 22615 * negPre, // the string to go in front of a negative number (e.g. `-` or `(`)) 22616 * posPre, // the string to go in front of a positive number 22617 * negSuf, // the string to go after a negative number (e.g. `)`) 22618 * posSuf // the string to go after a positive number 22619 * }} pattern 22620 * @param {string} groupSep The string to separate groups of number (e.g. `,`) 22621 * @param {string} decimalSep The string to act as the decimal separator (e.g. `.`) 22622 * @param {[type]} fractionSize The size of the fractional part of the number 22623 * @return {string} The number formatted as a string 22624 */ 22625function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 22626 22627 if (!(isString(number) || isNumber(number)) || isNaN(number)) return ''; 22628 22629 var isInfinity = !isFinite(number); 22630 var isZero = false; 22631 var numStr = Math.abs(number) + '', 22632 formattedText = '', 22633 parsedNumber; 22634 22635 if (isInfinity) { 22636 formattedText = '\u221e'; 22637 } else { 22638 parsedNumber = parse(numStr); 22639 22640 roundNumber(parsedNumber, fractionSize, pattern.minFrac, pattern.maxFrac); 22641 22642 var digits = parsedNumber.d; 22643 var integerLen = parsedNumber.i; 22644 var exponent = parsedNumber.e; 22645 var decimals = []; 22646 isZero = digits.reduce(function(isZero, d) { return isZero && !d; }, true); 22647 22648 // pad zeros for small numbers 22649 while (integerLen < 0) { 22650 digits.unshift(0); 22651 integerLen++; 22652 } 22653 22654 // extract decimals digits 22655 if (integerLen > 0) { 22656 decimals = digits.splice(integerLen); 22657 } else { 22658 decimals = digits; 22659 digits = [0]; 22660 } 22661 22662 // format the integer digits with grouping separators 22663 var groups = []; 22664 if (digits.length >= pattern.lgSize) { 22665 groups.unshift(digits.splice(-pattern.lgSize).join('')); 22666 } 22667 while (digits.length > pattern.gSize) { 22668 groups.unshift(digits.splice(-pattern.gSize).join('')); 22669 } 22670 if (digits.length) { 22671 groups.unshift(digits.join('')); 22672 } 22673 formattedText = groups.join(groupSep); 22674 22675 // append the decimal digits 22676 if (decimals.length) { 22677 formattedText += decimalSep + decimals.join(''); 22678 } 22679 22680 if (exponent) { 22681 formattedText += 'e+' + exponent; 22682 } 22683 } 22684 if (number < 0 && !isZero) { 22685 return pattern.negPre + formattedText + pattern.negSuf; 22686 } else { 22687 return pattern.posPre + formattedText + pattern.posSuf; 22688 } 22689} 22690 22691function padNumber(num, digits, trim, negWrap) { 22692 var neg = ''; 22693 if (num < 0 || (negWrap && num <= 0)) { 22694 if (negWrap) { 22695 num = -num + 1; 22696 } else { 22697 num = -num; 22698 neg = '-'; 22699 } 22700 } 22701 num = '' + num; 22702 while (num.length < digits) num = ZERO_CHAR + num; 22703 if (trim) { 22704 num = num.substr(num.length - digits); 22705 } 22706 return neg + num; 22707} 22708 22709 22710function dateGetter(name, size, offset, trim, negWrap) { 22711 offset = offset || 0; 22712 return function(date) { 22713 var value = date['get' + name](); 22714 if (offset > 0 || value > -offset) { 22715 value += offset; 22716 } 22717 if (value === 0 && offset == -12) value = 12; 22718 return padNumber(value, size, trim, negWrap); 22719 }; 22720} 22721 22722function dateStrGetter(name, shortForm, standAlone) { 22723 return function(date, formats) { 22724 var value = date['get' + name](); 22725 var propPrefix = (standAlone ? 'STANDALONE' : '') + (shortForm ? 'SHORT' : ''); 22726 var get = uppercase(propPrefix + name); 22727 22728 return formats[get][value]; 22729 }; 22730} 22731 22732function timeZoneGetter(date, formats, offset) { 22733 var zone = -1 * offset; 22734 var paddedZone = (zone >= 0) ? "+" : ""; 22735 22736 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 22737 padNumber(Math.abs(zone % 60), 2); 22738 22739 return paddedZone; 22740} 22741 22742function getFirstThursdayOfYear(year) { 22743 // 0 = index of January 22744 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 22745 // 4 = index of Thursday (+1 to account for 1st = 5) 22746 // 11 = index of *next* Thursday (+1 account for 1st = 12) 22747 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 22748} 22749 22750function getThursdayThisWeek(datetime) { 22751 return new Date(datetime.getFullYear(), datetime.getMonth(), 22752 // 4 = index of Thursday 22753 datetime.getDate() + (4 - datetime.getDay())); 22754} 22755 22756function weekGetter(size) { 22757 return function(date) { 22758 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 22759 thisThurs = getThursdayThisWeek(date); 22760 22761 var diff = +thisThurs - +firstThurs, 22762 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 22763 22764 return padNumber(result, size); 22765 }; 22766} 22767 22768function ampmGetter(date, formats) { 22769 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 22770} 22771 22772function eraGetter(date, formats) { 22773 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 22774} 22775 22776function longEraGetter(date, formats) { 22777 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 22778} 22779 22780var DATE_FORMATS = { 22781 yyyy: dateGetter('FullYear', 4, 0, false, true), 22782 yy: dateGetter('FullYear', 2, 0, true, true), 22783 y: dateGetter('FullYear', 1, 0, false, true), 22784 MMMM: dateStrGetter('Month'), 22785 MMM: dateStrGetter('Month', true), 22786 MM: dateGetter('Month', 2, 1), 22787 M: dateGetter('Month', 1, 1), 22788 LLLL: dateStrGetter('Month', false, true), 22789 dd: dateGetter('Date', 2), 22790 d: dateGetter('Date', 1), 22791 HH: dateGetter('Hours', 2), 22792 H: dateGetter('Hours', 1), 22793 hh: dateGetter('Hours', 2, -12), 22794 h: dateGetter('Hours', 1, -12), 22795 mm: dateGetter('Minutes', 2), 22796 m: dateGetter('Minutes', 1), 22797 ss: dateGetter('Seconds', 2), 22798 s: dateGetter('Seconds', 1), 22799 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 22800 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 22801 sss: dateGetter('Milliseconds', 3), 22802 EEEE: dateStrGetter('Day'), 22803 EEE: dateStrGetter('Day', true), 22804 a: ampmGetter, 22805 Z: timeZoneGetter, 22806 ww: weekGetter(2), 22807 w: weekGetter(1), 22808 G: eraGetter, 22809 GG: eraGetter, 22810 GGG: eraGetter, 22811 GGGG: longEraGetter 22812}; 22813 22814var DATE_FORMATS_SPLIT = /((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 22815 NUMBER_STRING = /^\-?\d+$/; 22816 22817/** 22818 * @ngdoc filter 22819 * @name date 22820 * @kind function 22821 * 22822 * @description 22823 * Formats `date` to a string based on the requested `format`. 22824 * 22825 * `format` string can be composed of the following elements: 22826 * 22827 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 22828 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 22829 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 22830 * * `'MMMM'`: Month in year (January-December) 22831 * * `'MMM'`: Month in year (Jan-Dec) 22832 * * `'MM'`: Month in year, padded (01-12) 22833 * * `'M'`: Month in year (1-12) 22834 * * `'LLLL'`: Stand-alone month in year (January-December) 22835 * * `'dd'`: Day in month, padded (01-31) 22836 * * `'d'`: Day in month (1-31) 22837 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 22838 * * `'EEE'`: Day in Week, (Sun-Sat) 22839 * * `'HH'`: Hour in day, padded (00-23) 22840 * * `'H'`: Hour in day (0-23) 22841 * * `'hh'`: Hour in AM/PM, padded (01-12) 22842 * * `'h'`: Hour in AM/PM, (1-12) 22843 * * `'mm'`: Minute in hour, padded (00-59) 22844 * * `'m'`: Minute in hour (0-59) 22845 * * `'ss'`: Second in minute, padded (00-59) 22846 * * `'s'`: Second in minute (0-59) 22847 * * `'sss'`: Millisecond in second, padded (000-999) 22848 * * `'a'`: AM/PM marker 22849 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 22850 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 22851 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 22852 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 22853 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 22854 * 22855 * `format` string can also be one of the following predefined 22856 * {@link guide/i18n localizable formats}: 22857 * 22858 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 22859 * (e.g. Sep 3, 2010 12:05:08 PM) 22860 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 22861 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 22862 * (e.g. Friday, September 3, 2010) 22863 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 22864 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 22865 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 22866 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 22867 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 22868 * 22869 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 22870 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 22871 * (e.g. `"h 'o''clock'"`). 22872 * 22873 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 22874 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 22875 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 22876 * specified in the string input, the time is considered to be in the local timezone. 22877 * @param {string=} format Formatting rules (see Description). If not specified, 22878 * `mediumDate` is used. 22879 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 22880 * continental US time zone abbreviations, but for general use, use a time zone offset, for 22881 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 22882 * If not specified, the timezone of the browser will be used. 22883 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 22884 * 22885 * @example 22886 <example> 22887 <file name="index.html">
22888 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 22889 <span>{{1288323623006 | date:'medium'}}</span><br> 22890 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 22891 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 22892 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 22893 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 22894 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 22895 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 22896 </file> 22897 <file name="protractor.js" type="protractor"> 22898 it('should format date', function() { 22899 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 22900 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 22901 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 22902 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 22903 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 22904 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 22905 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 22906 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 22907 }); 22908 </file> 22909 </example> 22910 */ 22911dateFilter.$inject = ['$locale']; 22912function dateFilter($locale) { 22913 22914 22915 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 22916 // 1 2 3 4 5 6 7 8 9 10 11 22917 function jsonStringToDate(string) { 22918 var match; 22919 if (match = string.match(R_ISO8601_STR)) { 22920 var date = new Date(0), 22921 tzHour = 0, 22922 tzMin = 0, 22923 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 22924 timeSetter = match[8] ? date.setUTCHours : date.setHours; 22925 22926 if (match[9]) { 22927 tzHour = toInt(match[9] + match[10]); 22928 tzMin = toInt(match[9] + match[11]); 22929 } 22930 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 22931 var h = toInt(match[4] || 0) - tzHour; 22932 var m = toInt(match[5] || 0) - tzMin; 22933 var s = toInt(match[6] || 0); 22934 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 22935 timeSetter.call(date, h, m, s, ms); 22936 return date; 22937 } 22938 return string; 22939 } 22940 22941 22942 return function(date, format, timezone) { 22943 var text = '', 22944 parts = [], 22945 fn, match; 22946 22947 format = format || 'mediumDate'; 22948 format = $locale.DATETIME_FORMATS[format] || format; 22949 if (isString(date)) { 22950 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 22951 } 22952 22953 if (isNumber(date)) { 22954 date = new Date(date); 22955 } 22956 22957 if (!isDate(date) || !isFinite(date.getTime())) { 22958 return date; 22959 } 22960 22961 while (format) { 22962 match = DATE_FORMATS_SPLIT.exec(format); 22963 if (match) { 22964 parts = concat(parts, match, 1); 22965 format = parts.pop(); 22966 } else { 22967 parts.push(format); 22968 format = null; 22969 } 22970 } 22971 22972 var dateTimezoneOffset = date.getTimezoneOffset(); 22973 if (timezone) { 22974 dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 22975 date = convertTimezoneToLocal(date, timezone, true); 22976 } 22977 forEach(parts, function(value) { 22978 fn = DATE_FORMATS[value]; 22979 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 22980 : value === "''" ? "'" : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 22981 }); 22982 22983 return text; 22984 }; 22985} 22986 22987 22988/** 22989 * @ngdoc filter 22990 * @name json 22991 * @kind function 22992 * 22993 * @description 22994 * Allows you to convert a JavaScript object into JSON string. 22995 * 22996 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 22997 * the binding is automatically converted to JSON. 22998 * 22999 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 23000 * @param {number=}
23000 spacing The number of spaces to use per indentation, defaults to 2. 23001 * @returns {string} JSON string. 23002 * 23003 * 23004 * @example 23005 <example> 23006 <file name="index.html"> 23007 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 23008 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 23009 </file> 23010 <file name="protractor.js" type="protractor"> 23011 it('should jsonify filtered objects', function() { 23012 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 23013 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 23014 }); 23015 </file> 23016 </example> 23017 * 23018 */ 23019function jsonFilter() { 23020 return function(object, spacing) { 23021 if (isUndefined(spacing)) { 23022 spacing = 2; 23023 } 23024 return toJson(object, spacing); 23025 }; 23026} 23027 23028 23029/** 23030 * @ngdoc filter 23031 * @name lowercase 23032 * @kind function 23033 * @description 23034 * Converts string to lowercase. 23035 * @see angular.lowercase 23036 */ 23037var lowercaseFilter = valueFn(lowercase); 23038 23039 23040/** 23041 * @ngdoc filter 23042 * @name uppercase 23043 * @kind function 23044 * @description 23045 * Converts string to uppercase. 23046 * @see angular.uppercase 23047 */ 23048var uppercaseFilter = valueFn(uppercase); 23049 23050/** 23051 * @ngdoc filter 23052 * @name limitTo 23053 * @kind function 23054 * 23055 * @description 23056 * Creates a new array or string containing only a specified number of elements. The elements 23057 * are taken from either the beginning or the end of the source array, string or number, as specified by 23058 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 23059 * converted to a string. 23060 * 23061 * @param {Array|string|number} input Source array, string or number to be limited. 23062 * @param {string|number} limit The length of the returned array or string. If the `limit` number 23063 * is positive, `limit` number of items from the beginning of the source array/string are copied. 23064 * If the number is negative, `limit` number of items from the end of the source array/string 23065 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 23066 * the input will be returned unchanged. 23067 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 23068 * indicates an offset from the end of `input`. Defaults to `0`. 23069 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 23070 * had less than `limit` elements. 23071 * 23072 * @example 23073 <example module="limitToExample"> 23074 <file name="index.html"> 23075 <script> 23076 angular.module('limitToExample', []) 23077 .controller('ExampleController', ['$scope', function($scope) { 23078 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 23079 $scope.letters = "abcdefghi"; 23080 $scope.longNumber = 2345432342; 23081 $scope.numLimit = 3; 23082 $scope.letterLimit = 3; 23083 $scope.longNumberLimit = 3; 23084 }]); 23085 </script>
23086 <div ng-controller="ExampleController"> 23087 <label> 23088 Limit {{numbers}} to: 23089 <input type="number" step="1" ng-model="numLimit"> 23090 </label> 23091 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 23092 <label> 23093 Limit {{letters}} to: 23094 <input type="number" step="1" ng-model="letterLimit"> 23095 </label> 23096 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 23097 <label> 23098 Limit {{longNumber}} to: 23099 <input type="number" step="1" ng-model="longNumberLimit"> 23100 </label> 23101 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 23102 </div> 23103 </file> 23104 <file name="protractor.js" type="protractor"> 23105 var numLimitInput = element(by.model('numLimit')); 23106 var letterLimitInput = element(by.model('letterLimit')); 23107 var longNumberLimitInput = element(by.model('longNumberLimit')); 23108 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 23109 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 23110 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 23111 23112 it('should limit the number array to first three items', function() { 23113 expect(numLimitInput.getAttribute('value')).toBe('3'); 23114 expect(letterLimitInput.getAttribute('value')).toBe('3'); 23115 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 23116 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 23117 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 23118 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 23119 }); 23120 23121 // There is a bug in safari and protractor that doesn't like the minus key 23122 // it('should update the output when -3 is entered', function() { 23123 // numLimitInput.clear(); 23124 // numLimitInput.sendKeys('-3'); 23125 // letterLimitInput.clear(); 23126 // letterLimitInput.sendKeys('-3'); 23127 // longNumberLimitInput.clear(); 23128 // longNumberLimitInput.sendKeys('-3'); 23129 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 23130 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 23131 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 23132 // }); 23133 23134 it('should not exceed the maximum size of input array', function() { 23135 numLimitInput.clear(); 23136 numLimitInput.sendKeys('100'); 23137 letterLimitInput.clear(); 23138 letterLimitInput.sendKeys('100'); 23139 longNumberLimitInput.clear(); 23140 longNumberLimitInput.sendKeys('100'); 23141 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 23142 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 23143 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 23144 }); 23145 </file> 23146 </example> 23147*/ 23148function limitToFilter() { 23149 return function(input, limit, begin) { 23150 if (Math.abs(Number(limit)) === Infinity) { 23151 limit = Number(limit); 23152 } else { 23153 limit = toInt(limit); 23154 } 23155 if (isNaN(limit)) return input; 23156 23157 if (isNumber(input)) input = input.toString(); 23158 if (!isArray(input) && !isString(input)) return input; 23159 23160 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 23161 begin = (begin < 0) ? Math.max(0, input.length + begin) : begin; 23162 23163 if (limit >= 0) { 23164 return input.slice(begin, begin + limit); 23165 } else { 23166 if (begin === 0) { 23167 return input.slice(limit, input.length); 23168 } else { 23169 return input.slice(Math.max(0, begin + limit), begin); 23170 } 23171 } 23172 }; 23173} 23174 23175/** 23176 * @ngdoc filter 23177 * @name orderBy 23178 * @kind function 23179 * 23180 * @description 23181 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 23182 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 23183 * as expected, make sure they are actually being saved as numbers and not strings. 23184 * Array-like values (e.g. NodeLists, jQuery objects, TypedArrays, Strings, etc) are also supported. 23185 * 23186 * @param {Array} array The array (or array-like object) to sort. 23187 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 23188 * used by the comparator to determine the order of elements. 23189 * 23190 * Can be one of: 23191 * 23192 * - `function`: Getter function. The result of this function will be sorted using the 23193 * `<`, `===`, `>` operator. 23194 * - `string`: An Angular expression. The result of this expression is used to compare elements 23195 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 23196 * 3 first characters of a property called `name`). The result of a constant expression 23197 * is interpreted as a property name to be used in comparisons (for example `"special name"` 23198 * to sort object by the value of their `special name` property). An expression can be 23199 * optionally prefixed with `+` or `-` to control ascending or descending sort order 23200 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 23201 * element itself is used to compare where sorting. 23202 * - `Array`: An array of function or string predicates. The first predicate in the array 23203 * is used for sorting, but when two items are equivalent, the next predicate is used. 23204 * 23205 * If the predicate is missing or empty then it defaults to `'+'`. 23206 * 23207 * @param {boolean=} reverse Reverse the order of the array. 23208 * @returns {Array} Sorted copy of the source array. 23209 * 23210 * 23211 * @example 23212 * The example below demonstrates a simple ngRepeat, where the data is sorted 23213 * by age in descending order (predicate is set to `'-age'`). 23214 * `reverse` is not set, which means it defaults to `false`. 23215 <example module="orderByExample"> 23216 <file name="index.html">
23217 <div ng-controller="ExampleController"> 23218 <table class="friend"> 23219 <tr> 23220 <th>Name</th> 23221 <th>Phone Number</th> 23222 <th>Age</th> 23223 </tr> 23224 <tr ng-repeat="friend in friends | orderBy:'-age'"> 23225 <td>{{friend.name}}</td> 23226 <td>{{friend.phone}}</td> 23227 <td>{{friend.age}}</td> 23228 </tr> 23229 </table> 23230 </div> 23231 </file> 23232 <file name="script.js"> 23233 angular.module('orderByExample', []) 23234 .controller('ExampleController', ['$scope', function($scope) { 23235 $scope.friends = 23236 [{name:'John', phone:'555-1212', age:10}, 23237 {name:'Mary', phone:'555-9876', age:19}, 23238 {name:'Mike', phone:'555-4321', age:21}, 23239 {name:'Adam', phone:'555-5678', age:35}, 23240 {name:'Julie', phone:'555-8765', age:29}]; 23241 }]); 23242 </file> 23243 </example> 23244 * 23245 * The predicate and reverse parameters can be controlled dynamically through scope properties, 23246 * as shown in the next example. 23247 * @example 23248 <example module="orderByExample"> 23249 <file name="index.html">
23250 <div ng-controller="ExampleController"> 23251 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 23252 <hr/> 23253 <button ng-click="predicate=''">Set to unsorted</button> 23254 <table class="friend"> 23255 <tr> 23256 <th> 23257 <button ng-click="order('name')">Name</button> 23258 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 23259 </th> 23260 <th> 23261 <button ng-click="order('phone')">Phone Number</button> 23262 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 23263 </th> 23264 <th> 23265 <button ng-click="order('age')">Age</button> 23266 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 23267 </th> 23268 </tr> 23269 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 23270 <td>{{friend.name}}</td> 23271 <td>{{friend.phone}}</td> 23272 <td>{{friend.age}}</td> 23273 </tr> 23274 </table> 23275 </div> 23276 </file> 23277 <file name="script.js"> 23278 angular.module('orderByExample', []) 23279 .controller('ExampleController', ['$scope', function($scope) { 23280 $scope.friends = 23281 [{name:'John', phone:'555-1212', age:10}, 23282 {name:'Mary', phone:'555-9876', age:19}, 23283 {name:'Mike', phone:'555-4321', age:21}, 23284 {name:'Adam', phone:'555-5678', age:35}, 23285 {name:'Julie', phone:'555-8765', age:29}]; 23286 $scope.predicate = 'age'; 23287 $scope.reverse = true; 23288 $scope.order = function(predicate) { 23289 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 23290 $scope.predicate = predicate; 23291 }; 23292 }]); 23293 </file> 23294 <file name="style.css"> 23295 .sortorder:after { 23296 content: '\25b2'; 23297 } 23298 .sortorder.reverse:after { 23299 content: '\25bc'; 23300 } 23301 </file> 23302 </example> 23303 * 23304 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 23305 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 23306 * desired parameters. 23307 * 23308 * Example: 23309 * 23310 * @example 23311 <example module="orderByExample"> 23312 <file name="index.html">
23313 <div ng-controller="ExampleController"> 23314 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 23315 <table class="friend"> 23316 <tr> 23317 <th> 23318 <button ng-click="order('name')">Name</button> 23319 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 23320 </th> 23321 <th> 23322 <button ng-click="order('phone')">Phone Number</button> 23323 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 23324 </th> 23325 <th> 23326 <button ng-click="order('age')">Age</button> 23327 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 23328 </th> 23329 </tr> 23330 <tr ng-repeat="friend in friends"> 23331 <td>{{friend.name}}</td> 23332 <td>{{friend.phone}}</td> 23333 <td>{{friend.age}}</td> 23334 </tr> 23335 </table> 23336 </div> 23337 </file> 23338 23339 <file name="script.js"> 23340 angular.module('orderByExample', []) 23341 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 23342 var orderBy = $filter('orderBy'); 23343 $scope.friends = [ 23344 { name: 'John', phone: '555-1212', age: 10 }, 23345 { name: 'Mary', phone: '555-9876', age: 19 }, 23346 { name: 'Mike', phone: '555-4321', age: 21 }, 23347 { name: 'Adam', phone: '555-5678', age: 35 }, 23348 { name: 'Julie', phone: '555-8765', age: 29 } 23349 ]; 23350 $scope.order = function(predicate) { 23351 $scope.predicate = predicate; 23352 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 23353 $scope.friends = orderBy($scope.friends, predicate, $scope.reverse); 23354 }; 23355 $scope.order('age', true); 23356 }]); 23357 </file> 23358 23359 <file name="style.css"> 23360 .sortorder:after { 23361 content: '\25b2'; 23362 } 23363 .sortorder.reverse:after { 23364 content: '\25bc'; 23365 } 23366 </file> 23367</example> 23368 */ 23369orderByFilter.$inject = ['$parse']; 23370function orderByFilter($parse) { 23371 return function(array, sortPredicate, reverseOrder) { 23372 23373 if (array == null) return array; 23374 if (!isArrayLike(array)) { 23375 throw minErr('orderBy')('notarray', 'Expected array but received: {0}', array); 23376 } 23377 23378 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 23379 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 23380 23381 var predicates = processPredicates(sortPredicate, reverseOrder); 23382 // Add a predicate at the end that evaluates to the element index. This makes the 23383 // sort stable as it works as a tie-breaker when all the input predicates cannot 23384 // distinguish between two elements. 23385 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1}); 23386 23387 // The next three lines are a version of a Swartzian Transform idiom from Perl 23388 // (sometimes called the Decorate-Sort-Undecorate idiom) 23389 // See https://en.wikipedia.org/wiki/Schwartzian_transform 23390 var compareValues = Array.prototype.map.call(array, getComparisonObject); 23391 compareValues.sort(doComparison); 23392 array = compareValues.map(function(item) { return item.value; }); 23393 23394 return array; 23395 23396 function getComparisonObject(value, index) { 23397 return { 23398 value: value, 23399 predicateValues: predicates.map(function(predicate) { 23400 return getPredicateValue(predicate.get(value), index); 23401 }) 23402 }; 23403 } 23404 23405 function doComparison(v1, v2) { 23406 var result = 0; 23407 for (var index=0, length = predicates.length; index < length; ++index) { 23408 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; 23409 if (result) break; 23410 } 23411 return result; 23412 } 23413 }; 23414 23415 function processPredicates(sortPredicate, reverseOrder) { 23416 reverseOrder = reverseOrder ? -1 : 1; 23417 return sortPredicate.map(function(predicate) { 23418 var descending = 1, get = identity; 23419 23420 if (isFunction(predicate)) { 23421 get = predicate; 23422 } else if (isString(predicate)) { 23423 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 23424 descending = predicate.charAt(0) == '-' ? -1 : 1; 23425 predicate = predicate.substring(1); 23426 } 23427 if (predicate !== '') { 23428 get = $parse(predicate); 23429 if (get.constant) { 23430 var key = get(); 23431 get = function(value) { return value[key]; }; 23432 } 23433 } 23434 }
23435 return { get: get, descending: descending * reverseOrder }; 23436 }); 23437 } 23438 23439 function isPrimitive(value) { 23440 switch (typeof value) { 23441 case 'number': /* falls through */ 23442 case 'boolean': /* falls through */ 23443 case 'string': 23444 return true; 23445 default: 23446 return false; 23447 } 23448 } 23449 23450 function objectValue(value, index) { 23451 // If `valueOf` is a valid function use that 23452 if (typeof value.valueOf === 'function') { 23453 value = value.valueOf(); 23454 if (isPrimitive(value)) return value; 23455 } 23456 // If `toString` is a valid function and not the one from `Object.prototype` use that 23457 if (hasCustomToString(value)) { 23458 value = value.toString(); 23459 if (isPrimitive(value)) return value; 23460 } 23461 // We have a basic object so we use the position of the object in the collection 23462 return index; 23463 } 23464 23465 function getPredicateValue(value, index) { 23466 var type = typeof value; 23467 if (value === null) { 23468 type = 'string'; 23469 value = 'null'; 23470 } else if (type === 'string') { 23471 value = value.toLowerCase(); 23472 } else if (type === 'object') { 23473 value = objectValue(value, index); 23474 } 23475 return { value: value, type: type }; 23476 } 23477 23478 function compare(v1, v2) { 23479 var result = 0; 23480 if (v1.type === v2.type) { 23481 if (v1.value !== v2.value) { 23482 result = v1.value < v2.value ? -1 : 1; 23483 } 23484 } else { 23485 result = v1.type < v2.type ? -1 : 1; 23486 } 23487 return result; 23488 } 23489} 23490 23491function ngDirective(directive) { 23492 if (isFunction(directive)) { 23493 directive = { 23494 link: directive 23495 }; 23496 } 23497 directive.restrict = directive.restrict || 'AC'; 23498 return valueFn(directive); 23499} 23500 23501/** 23502 * @ngdoc directive 23503 * @name a 23504 * @restrict E 23505 * 23506 * @description 23507 * Modifies the default behavior of the html A tag so that the default action is prevented when 23508 * the href attribute is empty. 23509 * 23510 * This change permits the easy creation of action links with the `ngClick` directive 23511 * without changing the location or causing page reloads, e.g.: 23512 * `<a href="" ng-click="list.addItem()">Add Item</a>` 23513 */ 23514var htmlAnchorDirective = valueFn({ 23515 restrict: 'E', 23516 compile: function(element, attr) { 23517 if (!attr.href && !attr.xlinkHref) { 23518 return function(scope, element) { 23519 // If the linked element is not an anchor tag anymore, do nothing 23520 if (element[0].nodeName.toLowerCase() !== 'a') return; 23521 23522 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 23523 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 23524 'xlink:href' : 'href'; 23525 element.on('click', function(event) { 23526 // if we have no href url, then don't navigate anywhere. 23527 if (!element.attr(href)) { 23528 event.preventDefault(); 23529 } 23530 }); 23531 }; 23532 } 23533 } 23534}); 23535 23536/** 23537 * @ngdoc directive 23538 * @name ngHref 23539 * @restrict A 23540 * @priority 99 23541 * 23542 * @description 23543 * Using Angular markup like `{{hash}}` in an href attribute will 23544 * make the link go to the wrong URL if the user clicks it before 23545 * Angular has a chance to replace the `{{hash}}` markup with its 23546 * value. Until Angular replaces the markup the link will be broken 23547 * and will most likely return a 404 error. The `ngHref` directive 23548 * solves this problem. 23549 * 23550 * The wrong way to write it: 23551 * ```html 23552 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 23553 * ``` 23554 * 23555 * The correct way to write it: 23556 * ```html 23557 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 23558 * ``` 23559 * 23560 * @element A 23561 * @param {template} ngHref any string which can contain `{{}}` markup. 23562 * 23563 * @example 23564 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 23565 * in links and their different behaviors: 23566 <example> 23567 <file name="index.html"> 23568 <input ng-model="value" /><br /> 23569 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 23570 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 23571 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 23572 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 23573 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 23574 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 23575 </file> 23576 <file name="protractor.js" type="protractor"> 23577 it('should execute ng-click but not reload when href without value', function() {
23578 element(by.id('link-1')).click(); 23579 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 23580 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 23581 }); 23582 23583 it('should execute ng-click but not reload when href empty string', function() { 23584 element(by.id('link-2')).click(); 23585 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 23586 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 23587 }); 23588 23589 it('should execute ng-click and change url when ng-href specified', function() { 23590 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 23591 23592 element(by.id('link-3')).click(); 23593 23594 // At this point, we navigate away from an Angular page, so we need 23595 // to use browser.driver to get the base webdriver. 23596 23597 browser.wait(function() { 23598 return browser.driver.getCurrentUrl().then(function(url) { 23599 return url.match(/\/123$/); 23600 }); 23601 }, 5000, 'page should navigate to /123'); 23602 }); 23603 23604 it('should execute ng-click but not reload when href empty string and name specified', function() { 23605 element(by.id('link-4')).click(); 23606 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 23607 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 23608 }); 23609 23610 it('should execute ng-click but not reload when no href but name specified', function() { 23611 element(by.id('link-5')).click(); 23612 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 23613 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 23614 }); 23615 23616 it('should only change url when only ng-href', function() { 23617 element(by.model('value')).clear(); 23618 element(by.model('value')).sendKeys('6'); 23619 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 23620 23621 element(by.id('link-6')).click(); 23622 23623 // At this point, we navigate away from an Angular page, so we need 23624 // to use browser.driver to get the base webdriver. 23625 browser.wait(function() { 23626 return browser.driver.getCurrentUrl().then(function(url) { 23627 return url.match(/\/6$/); 23628 }); 23629 }, 5000, 'page should navigate to /6'); 23630 }); 23631 </file> 23632 </example> 23633 */ 23634 23635/** 23636 * @ngdoc directive 23637 * @name ngSrc 23638 * @restrict A 23639 * @priority 99 23640 * 23641 * @description 23642 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 23643 * work right: The browser will fetch from the URL with the literal 23644 * text `{{hash}}` until Angular replaces the expression inside 23645 * `{{hash}}`. The `ngSrc` directive solves this problem. 23646 * 23647 * The buggy way to write it: 23648 * ```html 23649 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 23650 * ``` 23651 * 23652 * The correct way to write it: 23653 * ```html 23654 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 23655 * ``` 23656 * 23657 * @element IMG 23658 * @param {template} ngSrc any string which can contain `{{}}` markup. 23659 */ 23660 23661/** 23662 * @ngdoc directive 23663 * @name ngSrcset 23664 * @restrict A 23665 * @priority 99 23666 * 23667 * @description 23668 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 23669 * work right: The browser will fetch from the URL with the literal 23670 * text `{{hash}}` until Angular replaces the expression inside 23671 * `{{hash}}`. The `ngSrcset` directive solves this problem. 23672 * 23673 * The buggy way to write it: 23674 * ```html 23675 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 23676 * ``` 23677 * 23678 * The correct way to write it: 23679 * ```html 23680 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 23681 * ``` 23682 * 23683 * @element IMG 23684 * @param {template} ngSrcset any string which can contain `{{}}` markup. 23685 */ 23686 23687/** 23688 * @ngdoc directive 23689 * @name ngDisabled 23690 * @restrict A 23691 * @priority 100 23692 * 23693 * @description 23694 * 23695 * This directive sets the `disabled` attribute on the element if the 23696 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 23697 * 23698 * A special directive is necessary because we cannot use interpolation inside the `disabled` 23699 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 23700 * 23701 * @example 23702 <example> 23703 <file name="index.html"> 23704 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 23705 <button ng-model="button" ng-disabled="checked">Button</button> 23706 </file> 23707 <file name="protractor.js" type="protractor"> 23708 it('should toggle button', function() { 23709 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
23710 element(by.model('checked')).click(); 23711 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 23712 }); 23713 </file> 23714 </example> 23715 * 23716 * @element INPUT 23717 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 23718 * then the `disabled` attribute will be set on the element 23719 */ 23720 23721 23722/** 23723 * @ngdoc directive 23724 * @name ngChecked 23725 * @restrict A 23726 * @priority 100 23727 * 23728 * @description 23729 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 23730 * 23731 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 23732 * as this can lead to unexpected behavior. 23733 * 23734 * A special directive is necessary because we cannot use interpolation inside the `checked` 23735 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 23736 * 23737 * @example 23738 <example> 23739 <file name="index.html"> 23740 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 23741 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 23742 </file> 23743 <file name="protractor.js" type="protractor"> 23744 it('should check both checkBoxes', function() { 23745 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 23746 element(by.model('master')).click(); 23747 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 23748 }); 23749 </file> 23750 </example> 23751 * 23752 * @element INPUT 23753 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 23754 * then the `checked` attribute will be set on the element 23755 */ 23756 23757 23758/** 23759 * @ngdoc directive 23760 * @name ngReadonly 23761 * @restrict A 23762 * @priority 100 23763 * 23764 * @description 23765 * 23766 * Sets the `readOnly` attribute on the element, if the expression inside `ngReadonly` is truthy. 23767 * 23768 * A special directive is necessary because we cannot use interpolation inside the `readOnly` 23769 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 23770 * 23771 * @example 23772 <example> 23773 <file name="index.html"> 23774 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 23775 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 23776 </file> 23777 <file name="protractor.js" type="protractor"> 23778 it('should toggle readonly attr', function() { 23779 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 23780 element(by.model('checked')).click(); 23781 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 23782 }); 23783 </file> 23784 </example> 23785 * 23786 * @element INPUT 23787 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 23788 * then special attribute "readonly" will be set on the element 23789 */ 23790 23791 23792/** 23793 * @ngdoc directive 23794 * @name ngSelected 23795 * @restrict A 23796 * @priority 100 23797 * 23798 * @description 23799 * 23800 * Sets the `selected` attribute on the element, if the expression inside `ngSelected` is truthy. 23801 * 23802 * A special directive is necessary because we cannot use interpolation inside the `selected` 23803 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 23804 * 23805 * @example 23806 <example> 23807 <file name="index.html"> 23808 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 23809 <select aria-label="ngSelected demo"> 23810 <option>Hello!</option> 23811 <option id="greet" ng-selected="selected">Greetings!</option> 23812 </select> 23813 </file> 23814 <file name="protractor.js" type="protractor"> 23815 it('should select Greetings!', function() { 23816 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 23817 element(by.model('selected')).click(); 23818 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 23819 }); 23820 </file> 23821 </example> 23822 * 23823 * @element OPTION 23824 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 23825 * then special attribute "selected" will be set on the element 23826 */ 23827 23828/** 23829 * @ngdoc directive 23830 * @name ngOpen 23831 * @restrict A 23832 * @priority 100 23833 * 23834 * @description 23835 * 23836 * Sets the `open` attribute on the element, if the expression inside `ngOpen` is truthy. 23837 * 23838 * A special directive is necessary because we cannot use interpolation inside the `open` 23839 * attribute. See the {@link guide/interpolation interpolation guide} for more info. 23840 * 23841 * @example 23842 <example> 23843 <file name="index.html"> 23844 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 23845 <details id="details" ng-open="open"> 23846 <summary>Show/Hide me</summary> 23847 </details> 23848 </file> 23849 <file name="protractor.js" type="protractor"> 23850 it('should toggle open', function() { 23851 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
23852 element(by.model('open')).click(); 23853 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 23854 }); 23855 </file> 23856 </example> 23857 * 23858 * @element DETAILS 23859 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 23860 * then special attribute "open" will be set on the element 23861 */ 23862 23863var ngAttributeAliasDirectives = {}; 23864 23865// boolean attrs are evaluated 23866forEach(BOOLEAN_ATTR, function(propName, attrName) { 23867 // binding to multiple is not supported 23868 if (propName == "multiple") return; 23869 23870 function defaultLinkFn(scope, element, attr) { 23871 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 23872 attr.$set(attrName, !!value); 23873 }); 23874 } 23875 23876 var normalized = directiveNormalize('ng-' + attrName); 23877 var linkFn = defaultLinkFn; 23878 23879 if (propName === 'checked') { 23880 linkFn = function(scope, element, attr) { 23881 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 23882 if (attr.ngModel !== attr[normalized]) { 23883 defaultLinkFn(scope, element, attr); 23884 } 23885 }; 23886 } 23887 23888 ngAttributeAliasDirectives[normalized] = function() { 23889 return { 23890 restrict: 'A', 23891 priority: 100, 23892 link: linkFn 23893 }; 23894 }; 23895}); 23896 23897// aliased input attrs are evaluated 23898forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 23899 ngAttributeAliasDirectives[ngAttr] = function() { 23900 return { 23901 priority: 100, 23902 link: function(scope, element, attr) { 23903 //special case ngPattern when a literal regular expression value 23904 //is used as the expression (this way we don't have to watch anything). 23905 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 23906 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 23907 if (match) { 23908 attr.$set("ngPattern", new RegExp(match[1], match[2])); 23909 return; 23910 } 23911 } 23912 23913 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 23914 attr.$set(ngAttr, value); 23915 }); 23916 } 23917 }; 23918 }; 23919}); 23920 23921// ng-src, ng-srcset, ng-href are interpolated 23922forEach(['src', 'srcset', 'href'], function(attrName) { 23923 var normalized = directiveNormalize('ng-' + attrName); 23924 ngAttributeAliasDirectives[normalized] = function() { 23925 return { 23926 priority: 99, // it needs to run after the attributes are interpolated 23927 link: function(scope, element, attr) { 23928 var propName = attrName, 23929 name = attrName; 23930 23931 if (attrName === 'href' && 23932 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 23933 name = 'xlinkHref'; 23934 attr.$attr[name] = 'xlink:href'; 23935 propName = null; 23936 } 23937 23938 attr.$observe(normalized, function(value) { 23939 if (!value) { 23940 if (attrName === 'href') { 23941 attr.$set(name, null); 23942 } 23943 return; 23944 } 23945 23946 attr.$set(name, value); 23947 23948 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 23949 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 23950 // to set the property as well to achieve the desired effect. 23951 // we use attr[attrName] value since $set can sanitize the url. 23952 if (msie && propName) element.prop(propName, attr[name]); 23953 }); 23954 } 23955 }; 23956 }; 23957}); 23958 23959/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 23960 */ 23961var nullFormCtrl = { 23962 $addControl: noop, 23963 $$renameControl: nullFormRenameControl, 23964 $removeControl: noop, 23965 $setValidity: noop, 23966 $setDirty: noop, 23967 $setPristine: noop, 23968 $setSubmitted: noop 23969}, 23970SUBMITTED_CLASS = 'ng-submitted'; 23971 23972function nullFormRenameControl(control, name) { 23973 control.$name = name; 23974} 23975 23976/** 23977 * @ngdoc type 23978 * @name form.FormController 23979 * 23980 * @property {boolean} $pristine True if user has not interacted with the form yet. 23981 * @property {boolean} $dirty True if user has already interacted with the form. 23982 * @property {boolean} $valid True if all of the containing forms and controls are valid. 23983 * @property {boolean} $invalid True if at least one containing control or form is invali
23983d. 23984 * @property {boolean} $pending True if at least one containing control or form is pending. 23985 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 23986 * 23987 * @property {Object} $error Is an object hash, containing references to controls or 23988 * forms with failing validators, where: 23989 * 23990 * - keys are validation tokens (error names), 23991 * - values are arrays of controls or forms that have a failing validator for given error name. 23992 * 23993 * Built-in validation tokens: 23994 * 23995 * - `email` 23996 * - `max` 23997 * - `maxlength` 23998 * - `min` 23999 * - `minlength` 24000 * - `number` 24001 * - `pattern` 24002 * - `required` 24003 * - `url` 24004 * - `date` 24005 * - `datetimelocal` 24006 * - `time` 24007 * - `week` 24008 * - `month` 24009 * 24010 * @description 24011 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 24012 * such as being valid/invalid or dirty/pristine. 24013 * 24014 * Each {@link ng.directive:form form} directive creates an instance 24015 * of `FormController`. 24016 * 24017 */ 24018//asks for $scope to fool the BC controller module 24019FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 24020function FormController(element, attrs, $scope, $animate, $interpolate) { 24021 var form = this, 24022 controls = []; 24023 24024 // init state 24025 form.$error = {}; 24026 form.$$success = {}; 24027 form.$pending = undefined; 24028 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 24029 form.$dirty = false; 24030 form.$pristine = true; 24031 form.$valid = true; 24032 form.$invalid = false; 24033 form.$submitted = false; 24034 form.$$parentForm = nullFormCtrl; 24035 24036 /** 24037 * @ngdoc method 24038 * @name form.FormController#$rollbackViewValue 24039 * 24040 * @description 24041 * Rollback all form controls pending updates to the `$modelValue`. 24042 * 24043 * Updates may be pending by a debounced event or because the input is waiting for a some future 24044 * event defined in `ng-model-options`. This method is typically needed by the reset button of 24045 * a form that uses `ng-model-options` to pend updates. 24046 */ 24047 form.$rollbackViewValue = function() { 24048 forEach(controls, function(control) { 24049 control.$rollbackViewValue(); 24050 }); 24051 }; 24052 24053 /** 24054 * @ngdoc method 24055 * @name form.FormController#$commitViewValue 24056 * 24057 * @description 24058 * Commit all form controls pending updates to the `$modelValue`. 24059 * 24060 * Updates may be pending by a debounced event or because the input is waiting for a some future 24061 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 24062 * usually handles calling this in response to input events. 24063 */ 24064 form.$commitViewValue = function() { 24065 forEach(controls, function(control) { 24066 control.$commitViewValue(); 24067 }); 24068 }; 24069 24070 /** 24071 * @ngdoc method 24072 * @name form.FormController#$addControl 24073 * @param {object} control control object, either a {@link form.FormController} or an 24074 * {@link ngModel.NgModelController} 24075 * 24076 * @description 24077 * Register a control with the form. Input elements using ngModelController do this automatically 24078 * when they are linked. 24079 * 24080 * Note that the current state of the control will not be reflected on the new parent form. This 24081 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 24082 * state. 24083 * 24084 * However, if the method is used programmatically, for example by adding dynamically created controls, 24085 * or controls that have been previously removed without destroying their corresponding DOM element, 24086 * it's the developers responsibility to make sure the current state propagates to the parent form. 24087 * 24088 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 24089 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 24090 */ 24091 form.$addControl = function(control) { 24092 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 24093 // and not added to the scope. Now we throw an error. 24094 assertNotHasOwnProperty(control.$name, 'input'); 24095 controls.push(control); 24096 24097 if (control.$name) { 24098 form[control.$name] = control; 24099 } 24100 24101 control.$$parentForm = form; 24102 }; 24103 24104 // Private API: rename a form control 24105 form.$$renameControl = function(control, newName) { 24106 var oldName = control.$name; 24107 24108 if (form[oldName] === control) { 24109 delete form[oldName]; 24110 } 24111 form[newName] = control; 24112 control.$name = newName; 24113 }; 24114 24115 /** 24116 * @ngdoc method
24117 * @name form.FormController#$removeControl 24118 * @param {object} control control object, either a {@link form.FormController} or an 24119 * {@link ngModel.NgModelController} 24120 * 24121 * @description 24122 * Deregister a control from the form. 24123 * 24124 * Input elements using ngModelController do this automatically when they are destroyed. 24125 * 24126 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 24127 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 24128 * different from case to case. For example, removing the only `$dirty` control from a form may or 24129 * may not mean that the form is still `$dirty`. 24130 */ 24131 form.$removeControl = function(control) { 24132 if (control.$name && form[control.$name] === control) { 24133 delete form[control.$name]; 24134 } 24135 forEach(form.$pending, function(value, name) { 24136 form.$setValidity(name, null, control); 24137 }); 24138 forEach(form.$error, function(value, name) { 24139 form.$setValidity(name, null, control); 24140 }); 24141 forEach(form.$$success, function(value, name) { 24142 form.$setValidity(name, null, control); 24143 }); 24144 24145 arrayRemove(controls, control); 24146 control.$$parentForm = nullFormCtrl; 24147 }; 24148 24149 24150 /** 24151 * @ngdoc method 24152 * @name form.FormController#$setValidity 24153 * 24154 * @description 24155 * Sets the validity of a form control. 24156 * 24157 * This method will also propagate to parent forms. 24158 */ 24159 addSetValidityMethod({ 24160 ctrl: this, 24161 $element: element, 24162 set: function(object, property, controller) { 24163 var list = object[property]; 24164 if (!list) { 24165 object[property] = [controller]; 24166 } else { 24167 var index = list.indexOf(controller); 24168 if (index === -1) { 24169 list.push(controller); 24170 } 24171 } 24172 }, 24173 unset: function(object, property, controller) { 24174 var list = object[property]; 24175 if (!list) { 24176 return; 24177 } 24178 arrayRemove(list, controller); 24179 if (list.length === 0) { 24180 delete object[property]; 24181 } 24182 }, 24183 $animate: $animate 24184 }); 24185 24186 /** 24187 * @ngdoc method 24188 * @name form.FormController#$setDirty 24189 * 24190 * @description 24191 * Sets the form to a dirty state. 24192 * 24193 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 24194 * state (ng-dirty class). This method will also propagate to parent forms. 24195 */ 24196 form.$setDirty = function() { 24197 $animate.removeClass(element, PRISTINE_CLASS); 24198 $animate.addClass(element, DIRTY_CLASS); 24199 form.$dirty = true; 24200 form.$pristine = false; 24201 form.$$parentForm.$setDirty(); 24202 }; 24203 24204 /** 24205 * @ngdoc method 24206 * @name form.FormController#$setPristine 24207 * 24208 * @description 24209 * Sets the form to its pristine state. 24210 * 24211 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 24212 * state (ng-pristine class). This method will also propagate to all the controls contained 24213 * in this form. 24214 * 24215 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 24216 * saving or resetting it. 24217 */ 24218 form.$setPristine = function() { 24219 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 24220 form.$dirty = false; 24221 form.$pristine = true; 24222 form.$submitted = false; 24223 forEach(controls, function(control) { 24224 control.$setPristine(); 24225 }); 24226 }; 24227 24228 /** 24229 * @ngdoc method 24230 * @name form.FormController#$setUntouched 24231 * 24232 * @description 24233 * Sets the form to its untouched state. 24234 * 24235 * This method can be called to remove the 'ng-touched' class and set the form controls to their 24236 * untouched state (ng-untouched class). 24237 * 24238 * Setting a form controls back to their untouched state is often useful when setting the form 24239 * back to its pristine state. 24240 */ 24241 form.$setUntouched = function() { 24242 forEach(controls, function(control) { 24243 control.$setUntouched(); 24244 }); 24245 }; 24246 24247 /** 24248 * @ngdoc method 24249 * @name form.FormController#$setSubmitted 24250 * 24251 * @description 24252 * Sets the form to its submitted state. 24253 */ 24254 form.$setSubmitted = function() {
24255 $animate.addClass(element, SUBMITTED_CLASS); 24256 form.$submitted = true; 24257 form.$$parentForm.$setSubmitted(); 24258 }; 24259} 24260 24261/** 24262 * @ngdoc directive 24263 * @name ngForm 24264 * @restrict EAC 24265 * 24266 * @description 24267 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 24268 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 24269 * sub-group of controls needs to be determined. 24270 * 24271 * Note: the purpose of `ngForm` is to group controls, 24272 * but not to be a replacement for the `<form>` tag with all of its capabilities 24273 * (e.g. posting to the server, ...). 24274 * 24275 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 24276 * related scope, under this name. 24277 * 24278 */ 24279 24280 /** 24281 * @ngdoc directive 24282 * @name form 24283 * @restrict E 24284 * 24285 * @description 24286 * Directive that instantiates 24287 * {@link form.FormController FormController}. 24288 * 24289 * If the `name` attribute is specified, the form controller is published onto the current scope under 24290 * this name. 24291 * 24292 * # Alias: {@link ng.directive:ngForm `ngForm`} 24293 * 24294 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 24295 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 24296 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive, which behaves identically to 24297 * `form` but can be nested. Nested forms can be useful, for example, if the validity of a sub-group 24298 * of controls needs to be determined. 24299 * 24300 * # CSS classes 24301 * - `ng-valid` is set if the form is valid. 24302 * - `ng-invalid` is set if the form is invalid. 24303 * - `ng-pending` is set if the form is pending. 24304 * - `ng-pristine` is set if the form is pristine. 24305 * - `ng-dirty` is set if the form is dirty. 24306 * - `ng-submitted` is set if the form was submitted. 24307 * 24308 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 24309 * 24310 * 24311 * # Submitting a form and preventing the default action 24312 * 24313 * Since the role of forms in client-side Angular applications is different than in classical 24314 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 24315 * page reload that sends the data to the server. Instead some javascript logic should be triggered 24316 * to handle the form submission in an application-specific way. 24317 * 24318 * For this reason, Angular prevents the default action (form submission to the server) unless the 24319 * `<form>` element has an `action` attribute specified. 24320 * 24321 * You can use one of the following two ways to specify what javascript method should be called when 24322 * a form is submitted: 24323 * 24324 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 24325 * - {@link ng.directive:ngClick ngClick} directive on the first 24326 * button or input field of type submit (input[type=submit]) 24327 * 24328 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 24329 * or {@link ng.directive:ngClick ngClick} directives. 24330 * This is because of the following form submission rules in the HTML specification: 24331 * 24332 * - If a form has only one input field then hitting enter in this field triggers form submit 24333 * (`ngSubmit`) 24334 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 24335 * doesn't trigger submit 24336 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 24337 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 24338 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 24339 * 24340 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 24341 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 24342 * to have access to the updated model. 24343 * 24344 * ## Animation Hooks 24345 * 24346 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 24347 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 24348 * other validations that are performed within the form. Animations in ngForm are similar to how 24349 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 24350 * as JS animations. 24351 * 24352 * The following example shows a simple way to utilize CSS transitions to style a form element
24353 * that has been rendered as invalid after it has been validated: 24354 * 24355 * <pre> 24356 * //be sure to include ngAnimate as a module to hook into more 24357 * //advanced animations 24358 * .my-form { 24359 * transition:0.5s linear all; 24360 * background: white; 24361 * } 24362 * .my-form.ng-invalid { 24363 * background: red; 24364 * color:white; 24365 * } 24366 * </pre> 24367 * 24368 * @example 24369 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 24370 <file name="index.html"> 24371 <script> 24372 angular.module('formExample', []) 24373 .controller('FormController', ['$scope', function($scope) { 24374 $scope.userType = 'guest'; 24375 }]); 24376 </script> 24377 <style> 24378 .my-form { 24379 transition:all linear 0.5s; 24380 background: transparent; 24381 } 24382 .my-form.ng-invalid { 24383 background: red; 24384 } 24385 </style> 24386 <form name="myForm" ng-controller="FormController" class="my-form"> 24387 userType: <input name="input" ng-model="userType" required> 24388 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 24389 <code>userType = {{userType}}</code><br> 24390 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 24391 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 24392 <code>myForm.$valid = {{myForm.$valid}}</code><br> 24393 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 24394 </form> 24395 </file> 24396 <file name="protractor.js" type="protractor"> 24397 it('should initialize to model', function() { 24398 var userType = element(by.binding('userType')); 24399 var valid = element(by.binding('myForm.input.$valid')); 24400 24401 expect(userType.getText()).toContain('guest'); 24402 expect(valid.getText()).toContain('true'); 24403 }); 24404 24405 it('should be invalid if empty', function() { 24406 var userType = element(by.binding('userType')); 24407 var valid = element(by.binding('myForm.input.$valid')); 24408 var userInput = element(by.model('userType')); 24409 24410 userInput.clear(); 24411 userInput.sendKeys(''); 24412 24413 expect(userType.getText()).toEqual('userType ='); 24414 expect(valid.getText()).toContain('false'); 24415 }); 24416 </file> 24417 </example> 24418 * 24419 * @param {string=} name Name of the form. If specified, the form controller will be published into 24420 * related scope, under this name. 24421 */ 24422var formDirectiveFactory = function(isNgForm) { 24423 return ['$timeout', '$parse', function($timeout, $parse) { 24424 var formDirective = { 24425 name: 'form', 24426 restrict: isNgForm ? 'EAC' : 'E', 24427 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 24428 controller: FormController, 24429 compile: function ngFormCompile(formElement, attr) { 24430 // Setup initial state of the control 24431 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 24432 24433 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 24434 24435 return { 24436 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 24437 var controller = ctrls[0]; 24438 24439 // if `action` attr is not present on the form, prevent the default action (submission) 24440 if (!('action' in attr)) { 24441 // we can't use jq events because if a form is destroyed during submission the default 24442 // action is not prevented. see #1238 24443 // 24444 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 24445 // page reload if the form was destroyed by submission of the form via a click handler 24446 // on a button in the form. Looks like an IE9 specific bug. 24447 var handleFormSubmission = function(event) { 24448 scope.$apply(function() { 24449 controller.$commitViewValue(); 24450 controller.$setSubmitted(); 24451 }); 24452 24453 event.preventDefault(); 24454 }; 24455 24456 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 24457 24458 // unregister the preventDefault listener so that we don't not leak memory but in a 24459 // way that will achieve the prevention of the default action. 24460 formElement.on('$destroy', function() { 24461 $timeout(function() { 24462 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 24463 }, 0, false); 24464 }); 24465 } 24466 24467 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 24468 parentFormCtrl.$addControl(controller); 24469 24470 var setter = nameAttr ? getSetter(controller.$name) : noop; 24471 24472 if (nameAttr) { 24473 setter(scope, controller); 24474 attr.$observe(nameAttr, function(newValue) { 24475 if (controller.$name === newValue) return; 24476 setter(scope, undefined); 24477 controller.$$parentForm.$$renameControl(controller, newValue); 24478 setter = getSetter(controller.$name); 24479 setter(scope, controller); 24480 }); 24481 } 24482 formElement.on('$destroy', function() { 24483 controller.$$parentForm.$removeControl(controller); 24484 setter(scope, undefined); 24485 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 24486 }); 24487 } 24488 }; 24489 } 24490 }; 24491 24492 return formDirective; 24493 24494 function getSetter(expression) { 24495 if (expression === '') { 24496 //create an assignable expression, so forms with an empty name can be renamed later 24497 return $parse('this[""]').assign; 24498 } 24499 return $parse(expression).assign || noop; 24500 } 24501 }]; 24502}; 24503 24504var formDirective = formDirectiveFactory(); 24505var ngFormDirective = formDirectiveFactory(true); 24506 24507/* global VALID_CLASS: false, 24508 INVALID_CLASS: false, 24509 PRISTINE_CLASS: false, 24510 DIRTY_CLASS: false, 24511 UNTOUCHED_CLASS: false, 24512 TOUCHED_CLASS: false, 24513 ngModelMinErr: false, 24514*/ 24515 24516// Regex code was initially obtained from SO prior to modification: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 24517var ISO_DATE_REGEXP = /^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/;
24518// See valid URLs in RFC3987 (http://tools.ietf.org/html/rfc3987) 24519// Note: We are being more lenient, because browsers are too. 24520// 1. Scheme 24521// 2. Slashes 24522// 3. Username 24523// 4. Password 24524// 5. Hostname 24525// 6. Port 24526// 7. Path 24527// 8. Query 24528// 9. Fragment 24529// 1111111111111111 222 333333 44444 555555555555555555555555 666 77777777 8888888 999 24530var URL_REGEXP = /^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i; 24531var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 24532var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 24533var DATE_REGEXP = /^(\d{4,})-(\d{2})-(\d{2})$/; 24534var DATETIMELOCAL_REGEXP = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 24535var WEEK_REGEXP = /^(\d{4,})-W(\d\d)$/; 24536var MONTH_REGEXP = /^(\d{4,})-(\d\d)$/; 24537var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 24538 24539var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown'; 24540var PARTIAL_VALIDATION_TYPES = createMap(); 24541forEach('date,datetime-local,month,time,week'.split(','), function(type) { 24542 PARTIAL_VALIDATION_TYPES[type] = true; 24543}); 24544 24545var inputType = { 24546 24547 /** 24548 * @ngdoc input 24549 * @name input[text] 24550 * 24551 * @description 24552 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 24553 * 24554 * 24555 * @param {string} ngModel Assignable angular expression to data-bind to. 24556 * @param {string=} name Property name of the form under which the control is published. 24557 * @param {string=} required Adds `required` validation error key if the value is not entered. 24558 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 24559 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 24560 * `required` when you want to data-bind to the `required` attribute. 24561 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 24562 * minlength. 24563 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 24564 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 24565 * any length. 24566 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 24567 * that contains the regular expression body that will be converted to a regular expression 24568 * as in the ngPattern directive. 24569 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 24570 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 24571 * If the expression evaluates to a RegExp object, then this is used directly. 24572 * If the expression evaluates to a string, then it will be converted to a RegExp 24573 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 24574 * `new RegExp('^abc$')`.<br /> 24575 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 24576 * start at the index of the last search's match, thus not taking the whole input value into 24577 * account. 24578 * @param {string=} ngChange Angular expression to be executed when input changes due to user 24579 * interaction with the input element. 24580 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 24581 * This parameter is ignored for input[type=password] controls, which will never trim the 24582 * input. 24583 * 24584 * @example 24585 <example name="text-input-directive" module="textInputExample"> 24586 <file name="index.html"> 24587 <script> 24588 angular.module('textInputExample', []) 24589 .controller('ExampleController', ['$scope', function($scope) { 24590 $scope.example = { 24591 text: 'guest', 24592 word: /^\s*\w*\s*$/ 24593 }; 24594 }]); 24595 </script>
24596 <form name="myForm" ng-controller="ExampleController"> 24597 <label>Single word: 24598 <input type="text" name="input" ng-model="example.text" 24599 ng-pattern="example.word" required ng-trim="false"> 24600 </label> 24601 <div role="alert"> 24602 <span class="error" ng-show="myForm.input.$error.required"> 24603 Required!</span> 24604 <span class="error" ng-show="myForm.input.$error.pattern"> 24605 Single word only!</span> 24606 </div> 24607 <tt>text = {{example.text}}</tt><br/> 24608 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 24609 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 24610 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24611 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24612 </form> 24613 </file> 24614 <file name="protractor.js" type="protractor"> 24615 var text = element(by.binding('example.text')); 24616 var valid = element(by.binding('myForm.input.$valid')); 24617 var input = element(by.model('example.text')); 24618 24619 it('should initialize to model', function() { 24620 expect(text.getText()).toContain('guest'); 24621 expect(valid.getText()).toContain('true'); 24622 }); 24623 24624 it('should be invalid if empty', function() { 24625 input.clear(); 24626 input.sendKeys(''); 24627 24628 expect(text.getText()).toEqual('text ='); 24629 expect(valid.getText()).toContain('false'); 24630 }); 24631 24632 it('should be invalid if multi word', function() { 24633 input.clear(); 24634 input.sendKeys('hello world'); 24635 24636 expect(valid.getText()).toContain('false'); 24637 }); 24638 </file> 24639 </example> 24640 */ 24641 'text': textInputType, 24642 24643 /** 24644 * @ngdoc input 24645 * @name input[date] 24646 * 24647 * @description 24648 * Input with date validation and transformation. In browsers that do not yet support 24649 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 24650 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 24651 * modern browsers do not yet support this input type, it is important to provide cues to users on the 24652 * expected input format via a placeholder or label. 24653 * 24654 * The model must always be a Date object, otherwise Angular will throw an error. 24655 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 24656 * 24657 * The timezone to be used to read/write the `Date` instance in the model can be defined using 24658 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 24659 * 24660 * @param {string} ngModel Assignable angular expression to data-bind to. 24661 * @param {string=} name Property name of the form under which the control is published. 24662 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 24663 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 24664 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 24665 * constraint validation. 24666 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 24667 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 24668 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 24669 * constraint validation. 24670 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 24671 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 24672 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 24673 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 24674 * @param {string=} required Sets `required` validation error key if the value is not entered. 24675 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 24676 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 24677 * `required` when you want to data-bind to the `required` attribute. 24678 * @param {string=} ngChange Angular expression to be executed when input changes due to user 24679 * interaction with the input element. 24680 * 24681 * @example 24682 <example name="date-input-directive" module="dateInputExample"> 24683 <file name="index.html"> 24684 <script> 24685 angular.module('dateInputExample', []) 24686 .controller('DateController', ['$scope', function($scope) { 24687 $scope.example = { 24688 value: new Date(2013, 9, 22) 24689 }; 24690 }]); 24691 </script> 24692 <form name="myForm" ng-controller="DateController as dateCtrl"> 24693 <label for="exampleInput">Pick a date in 2013:</label> 24694 <input type="date" id="exampleInput" name="input" ng-model="example.value" 24695 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 24696 <div role="alert">
24697 <span class="error" ng-show="myForm.input.$error.required"> 24698 Required!</span> 24699 <span class="error" ng-show="myForm.input.$error.date"> 24700 Not a valid date!</span> 24701 </div> 24702 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 24703 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 24704 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 24705 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24706 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24707 </form> 24708 </file> 24709 <file name="protractor.js" type="protractor"> 24710 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 24711 var valid = element(by.binding('myForm.input.$valid')); 24712 var input = element(by.model('example.value')); 24713 24714 // currently protractor/webdriver does not support 24715 // sending keys to all known HTML5 input controls 24716 // for various browsers (see https://github.com/angular/protractor/issues/562). 24717 function setInput(val) { 24718 // set the value of the element and force validation. 24719 var scr = "var ipt = document.getElementById('exampleInput'); " + 24720 "ipt.value = '" + val + "';" + 24721 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 24722 browser.executeScript(scr); 24723 } 24724 24725 it('should initialize to model', function() { 24726 expect(value.getText()).toContain('2013-10-22'); 24727 expect(valid.getText()).toContain('myForm.input.$valid = true'); 24728 }); 24729 24730 it('should be invalid if empty', function() { 24731 setInput(''); 24732 expect(value.getText()).toEqual('value ='); 24733 expect(valid.getText()).toContain('myForm.input.$valid = false'); 24734 }); 24735 24736 it('should be invalid if over max', function() { 24737 setInput('2015-01-01'); 24738 expect(value.getText()).toContain(''); 24739 expect(valid.getText()).toContain('myForm.input.$valid = false'); 24740 }); 24741 </file> 24742 </example> 24743 */ 24744 'date': createDateInputType('date', DATE_REGEXP, 24745 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 24746 'yyyy-MM-dd'), 24747 24748 /** 24749 * @ngdoc input 24750 * @name input[datetime-local] 24751 * 24752 * @description 24753 * Input with datetime validation and transformation. In browsers that do not yet support 24754 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 24755 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 24756 * 24757 * The model must always be a Date object, otherwise Angular will throw an error. 24758 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 24759 * 24760 * The timezone to be used to read/write the `Date` instance in the model can be defined using 24761 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 24762 * 24763 * @param {string} ngModel Assignable angular expression to data-bind to. 24764 * @param {string=} name Property name of the form under which the control is published. 24765 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 24766 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 24767 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 24768 * Note that `min` will also add native HTML5 constraint validation. 24769 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 24770 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 24771 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 24772 * Note that `max` will also add native HTML5 constraint validation. 24773 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 24774 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 24775 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 24776 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 24777 * @param {string=} required Sets `required` validation error key if the value is not entered. 24778 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 24779 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 24780 * `required` when you want to data-bind to the `required` attribute. 24781 * @param {string=} ngChange Angular expression to be executed when input changes due to user 24782 * interaction with the input element. 24783 * 24784 * @example 24785 <example name="datetimelocal-input-directive" module="dateExample"> 24786 <file name="index.html"> 24787 <script> 24788 angular.module('dateExample', []) 24789 .controller('DateController', ['$scope', function($scope) { 24790 $scope.example = { 24791 value: new Date(2010, 11, 28, 14, 57) 24792 }; 24793 }]); 24794 </script> 24795 <form name="myForm" ng-controller="DateController as dateCtrl"> 24796 <label for="exampleInput">Pick a date between in 2013:</label> 24797 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 24798 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 24799 <div role="alert">
24800 <span class="error" ng-show="myForm.input.$error.required"> 24801 Required!</span> 24802 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 24803 Not a valid date!</span> 24804 </div> 24805 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 24806 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 24807 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 24808 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24809 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24810 </form> 24811 </file> 24812 <file name="protractor.js" type="protractor"> 24813 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 24814 var valid = element(by.binding('myForm.input.$valid')); 24815 var input = element(by.model('example.value')); 24816 24817 // currently protractor/webdriver does not support 24818 // sending keys to all known HTML5 input controls 24819 // for various browsers (https://github.com/angular/protractor/issues/562). 24820 function setInput(val) { 24821 // set the value of the element and force validation. 24822 var scr = "var ipt = document.getElementById('exampleInput'); " + 24823 "ipt.value = '" + val + "';" + 24824 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 24825 browser.executeScript(scr); 24826 } 24827 24828 it('should initialize to model', function() { 24829 expect(value.getText()).toContain('2010-12-28T14:57:00'); 24830 expect(valid.getText()).toContain('myForm.input.$valid = true'); 24831 }); 24832 24833 it('should be invalid if empty', function() { 24834 setInput(''); 24835 expect(value.getText()).toEqual('value ='); 24836 expect(valid.getText()).toContain('myForm.input.$valid = false'); 24837 }); 24838 24839 it('should be invalid if over max', function() { 24840 setInput('2015-01-01T23:59:00'); 24841 expect(value.getText()).toContain(''); 24842 expect(valid.getText()).toContain('myForm.input.$valid = false'); 24843 }); 24844 </file> 24845 </example> 24846 */ 24847 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 24848 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 24849 'yyyy-MM-ddTHH:mm:ss.sss'), 24850 24851 /** 24852 * @ngdoc input 24853 * @name input[time] 24854 * 24855 * @description 24856 * Input with time validation and transformation. In browsers that do not yet support 24857 * the HTML5 time input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 24858 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 24859 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 24860 * 24861 * The model must always be a Date object, otherwise Angular will throw an error. 24862 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 24863 * 24864 * The timezone to be used to read/write the `Date` instance in the model can be defined using 24865 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 24866 * 24867 * @param {string} ngModel Assignable angular expression to data-bind to. 24868 * @param {string=} name Property name of the form under which the control is published. 24869 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 24870 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 24871 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 24872 * native HTML5 constraint validation. 24873 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 24874 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 24875 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 24876 * native HTML5 constraint validation. 24877 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 24878 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 24879 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 24880 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 24881 * @param {string=} required Sets `required` validation error key if the value is not entered. 24882 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 24883 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 24884 * `required` when you want to data-bind to the `required` attribute. 24885 * @param {string=} ngChange Angular expression to be executed when input changes due to user 24886 * interaction with the input element. 24887 * 24888 * @example 24889 <example name="time-input-directive" module="timeExample"> 24890 <file name="index.html"> 24891 <script> 24892 angular.module('timeExample', []) 24893 .controller('DateController', ['$scope', function($scope) { 24894 $scope.example = { 24895 value: new Date(1970, 0, 1, 14, 57, 0) 24896 }; 24897 }]); 24898 </script> 24899 <form name="myForm" ng-controller="DateController as dateCtrl"> 24900 <label for="exampleInput">Pick a time between 8am and 5pm:</label> 24901 <input type="time" id="exampleInput" name="input" ng-model="example.value" 24902 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 24903 <div role="alert">
24904 <span class="error" ng-show="myForm.input.$error.required"> 24905 Required!</span> 24906 <span class="error" ng-show="myForm.input.$error.time"> 24907 Not a valid date!</span> 24908 </div> 24909 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 24910 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 24911 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 24912 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24913 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24914 </form> 24915 </file> 24916 <file name="protractor.js" type="protractor"> 24917 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 24918 var valid = element(by.binding('myForm.input.$valid')); 24919 var input = element(by.model('example.value')); 24920 24921 // currently protractor/webdriver does not support 24922 // sending keys to all known HTML5 input controls 24923 // for various browsers (https://github.com/angular/protractor/issues/562). 24924 function setInput(val) { 24925 // set the value of the element and force validation. 24926 var scr = "var ipt = document.getElementById('exampleInput'); " + 24927 "ipt.value = '" + val + "';" + 24928 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 24929 browser.executeScript(scr); 24930 } 24931 24932 it('should initialize to model', function() { 24933 expect(value.getText()).toContain('14:57:00'); 24934 expect(valid.getText()).toContain('myForm.input.$valid = true'); 24935 }); 24936 24937 it('should be invalid if empty', function() { 24938 setInput(''); 24939 expect(value.getText()).toEqual('value ='); 24940 expect(valid.getText()).toContain('myForm.input.$valid = false'); 24941 }); 24942 24943 it('should be invalid if over max', function() { 24944 setInput('23:59:00'); 24945 expect(value.getText()).toContain(''); 24946 expect(valid.getText()).toContain('myForm.input.$valid = false'); 24947 }); 24948 </file> 24949 </example> 24950 */ 24951 'time': createDateInputType('time', TIME_REGEXP, 24952 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 24953 'HH:mm:ss.sss'), 24954 24955 /** 24956 * @ngdoc input 24957 * @name input[week] 24958 * 24959 * @description 24960 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 24961 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 24962 * week format (yyyy-W##), for example: `2013-W02`. 24963 * 24964 * The model must always be a Date object, otherwise Angular will throw an error. 24965 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 24966 * 24967 * The timezone to be used to read/write the `Date` instance in the model can be defined using 24968 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 24969 * 24970 * @param {string} ngModel Assignable angular expression to data-bind to. 24971 * @param {string=} name Property name of the form under which the control is published. 24972 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 24973 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 24974 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 24975 * native HTML5 constraint validation. 24976 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 24977 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 24978 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 24979 * native HTML5 constraint validation. 24980 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 24981 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 24982 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 24983 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 24984 * @param {string=} required Sets `required` validation error key if the value is not entered. 24985 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 24986 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 24987 * `required` when you want to data-bind to the `required` attribute. 24988 * @param {string=} ngChange Angular expression to be executed when input changes due to user 24989 * interaction with the input element. 24990 * 24991 * @example 24992 <example name="week-input-directive" module="weekExample"> 24993 <file name="index.html"> 24994 <script> 24995 angular.module('weekExample', []) 24996 .controller('DateController', ['$scope', function($scope) { 24997 $scope.example = { 24998 value: new Date(2013, 0, 3) 24999 }; 25000 }]); 25001 </script> 25002 <form name="myForm" ng-controller="DateController as dateCtrl"> 25003 <label>Pick a date between in 2013: 25004 <input id="exampleInput" type="week" name="input" ng-model="example.value" 25005 placeholder="YYYY-W##" min="2012-W32" 25006 max="2013-W52" required /> 25007 </label> 25008 <div role="alert">
25009 <span class="error" ng-show="myForm.input.$error.required"> 25010 Required!</span> 25011 <span class="error" ng-show="myForm.input.$error.week"> 25012 Not a valid date!</span> 25013 </div> 25014 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 25015 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25016 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25017 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25018 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25019 </form> 25020 </file> 25021 <file name="protractor.js" type="protractor"> 25022 var value = element(by.binding('example.value | date: "yyyy-Www"')); 25023 var valid = element(by.binding('myForm.input.$valid')); 25024 var input = element(by.model('example.value')); 25025 25026 // currently protractor/webdriver does not support 25027 // sending keys to all known HTML5 input controls 25028 // for various browsers (https://github.com/angular/protractor/issues/562). 25029 function setInput(val) { 25030 // set the value of the element and force validation. 25031 var scr = "var ipt = document.getElementById('exampleInput'); " + 25032 "ipt.value = '" + val + "';" + 25033 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25034 browser.executeScript(scr); 25035 } 25036 25037 it('should initialize to model', function() { 25038 expect(value.getText()).toContain('2013-W01'); 25039 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25040 }); 25041 25042 it('should be invalid if empty', function() { 25043 setInput(''); 25044 expect(value.getText()).toEqual('value ='); 25045 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25046 }); 25047 25048 it('should be invalid if over max', function() { 25049 setInput('2015-W01'); 25050 expect(value.getText()).toContain(''); 25051 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25052 }); 25053 </file> 25054 </example> 25055 */ 25056 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 25057 25058 /** 25059 * @ngdoc input 25060 * @name input[month] 25061 * 25062 * @description 25063 * Input with month validation and transformation. In browsers that do not yet support 25064 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 25065 * month format (yyyy-MM), for example: `2009-01`. 25066 * 25067 * The model must always be a Date object, otherwise Angular will throw an error. 25068 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 25069 * If the model is not set to the first of the month, the next view to model update will set it 25070 * to the first of the month. 25071 * 25072 * The timezone to be used to read/write the `Date` instance in the model can be defined using 25073 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 25074 * 25075 * @param {string} ngModel Assignable angular expression to data-bind to. 25076 * @param {string=} name Property name of the form under which the control is published. 25077 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25078 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 25079 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 25080 * native HTML5 constraint validation. 25081 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25082 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 25083 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 25084 * native HTML5 constraint validation. 25085 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 25086 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 25087 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 25088 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 25089 25090 * @param {string=} required Sets `required` validation error key if the value is not entered. 25091 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25092 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25093 * `required` when you want to data-bind to the `required` attribute. 25094 * @param {string=} ngChange Angular expression to be executed when input changes due to user 25095 * interaction with the input element. 25096 * 25097 * @example 25098 <example name="month-input-directive" module="monthExample"> 25099 <file name="index.html"> 25100 <script> 25101 angular.module('monthExample', []) 25102 .controller('DateController', ['$scope', function($scope) { 25103 $scope.example = { 25104 value: new Date(2013, 9, 1) 25105 }; 25106 }]); 25107 </script> 25108 <form name="myForm" ng-controller="DateController as dateCtrl"> 25109 <label for="exampleInput">Pick a month in 2013:</label> 25110 <input id="exampleInput" type="month" name="input" ng-model="example.value" 25111 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 25112 <div role="alert">
25113 <span class="error" ng-show="myForm.input.$error.required"> 25114 Required!</span> 25115 <span class="error" ng-show="myForm.input.$error.month"> 25116 Not a valid month!</span> 25117 </div> 25118 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 25119 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25120 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25121 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25122 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25123 </form> 25124 </file> 25125 <file name="protractor.js" type="protractor"> 25126 var value = element(by.binding('example.value | date: "yyyy-MM"')); 25127 var valid = element(by.binding('myForm.input.$valid')); 25128 var input = element(by.model('example.value')); 25129 25130 // currently protractor/webdriver does not support 25131 // sending keys to all known HTML5 input controls 25132 // for various browsers (https://github.com/angular/protractor/issues/562). 25133 function setInput(val) { 25134 // set the value of the element and force validation. 25135 var scr = "var ipt = document.getElementById('exampleInput'); " + 25136 "ipt.value = '" + val + "';" + 25137 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 25138 browser.executeScript(scr); 25139 } 25140 25141 it('should initialize to model', function() { 25142 expect(value.getText()).toContain('2013-10'); 25143 expect(valid.getText()).toContain('myForm.input.$valid = true'); 25144 }); 25145 25146 it('should be invalid if empty', function() { 25147 setInput(''); 25148 expect(value.getText()).toEqual('value ='); 25149 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25150 }); 25151 25152 it('should be invalid if over max', function() { 25153 setInput('2015-01'); 25154 expect(value.getText()).toContain(''); 25155 expect(valid.getText()).toContain('myForm.input.$valid = false'); 25156 }); 25157 </file> 25158 </example> 25159 */ 25160 'month': createDateInputType('month', MONTH_REGEXP, 25161 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 25162 'yyyy-MM'), 25163 25164 /** 25165 * @ngdoc input 25166 * @name input[number] 25167 * 25168 * @description 25169 * Text input with number validation and transformation. Sets the `number` validation 25170 * error if not a valid number. 25171 * 25172 * <div class="alert alert-warning"> 25173 * The model must always be of type `number` otherwise Angular will throw an error. 25174 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 25175 * error docs for more information and an example of how to convert your model if necessary. 25176 * </div> 25177 * 25178 * ## Issues with HTML5 constraint validation 25179 * 25180 * In browsers that follow the 25181 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 25182 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 25183 * If a non-number is entered in the input, the browser will report the value as an empty string, 25184 * which means the view / model values in `ngModel` and subsequently the scope value 25185 * will also be an empty string. 25186 * 25187 * 25188 * @param {string} ngModel Assignable angular expression to data-bind to. 25189 * @param {string=} name Property name of the form under which the control is published. 25190 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 25191 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 25192 * @param {string=} required Sets `required` validation error key if the value is not entered. 25193 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25194 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25195 * `required` when you want to data-bind to the `required` attribute. 25196 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 25197 * minlength. 25198 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 25199 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 25200 * any length. 25201 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 25202 * that contains the regular expression body that will be converted to a regular expression 25203 * as in the ngPattern directive. 25204 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 25205 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 25206 * If the expression evaluates to a RegExp object, then this is used directly. 25207 * If the expression evaluates to a string, then it will be converted to a RegExp 25208 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 25209 * `new RegExp('^abc$')`.<br /> 25210 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 25211 * start at the index of the last search's match, thus not taking the whole input value into 25212 * account. 25213 * @param {string=} ngChange Angular expression to be executed when input changes due to user 25214 * interaction with the input element. 25215 * 25216 * @example 25217 <example name="number-input-directive" module="numberExample"> 25218 <file name="index.html"> 25219 <script> 25220 angular.module('numberExample', [])
25221 .controller('ExampleController', ['$scope', function($scope) { 25222 $scope.example = { 25223 value: 12 25224 }; 25225 }]); 25226 </script> 25227 <form name="myForm" ng-controller="ExampleController"> 25228 <label>Number: 25229 <input type="number" name="input" ng-model="example.value" 25230 min="0" max="99" required> 25231 </label> 25232 <div role="alert"> 25233 <span class="error" ng-show="myForm.input.$error.required"> 25234 Required!</span> 25235 <span class="error" ng-show="myForm.input.$error.number"> 25236 Not valid number!</span> 25237 </div> 25238 <tt>value = {{example.value}}</tt><br/> 25239 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25240 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25241 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25242 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25243 </form> 25244 </file> 25245 <file name="protractor.js" type="protractor"> 25246 var value = element(by.binding('example.value')); 25247 var valid = element(by.binding('myForm.input.$valid')); 25248 var input = element(by.model('example.value')); 25249 25250 it('should initialize to model', function() { 25251 expect(value.getText()).toContain('12'); 25252 expect(valid.getText()).toContain('true'); 25253 }); 25254 25255 it('should be invalid if empty', function() { 25256 input.clear(); 25257 input.sendKeys(''); 25258 expect(value.getText()).toEqual('value ='); 25259 expect(valid.getText()).toContain('false'); 25260 }); 25261 25262 it('should be invalid if over max', function() { 25263 input.clear(); 25264 input.sendKeys('123'); 25265 expect(value.getText()).toEqual('value ='); 25266 expect(valid.getText()).toContain('false'); 25267 }); 25268 </file> 25269 </example> 25270 */ 25271 'number': numberInputType, 25272 25273 25274 /** 25275 * @ngdoc input 25276 * @name input[url] 25277 * 25278 * @description 25279 * Text input with URL validation. Sets the `url` validation error key if the content is not a 25280 * valid URL. 25281 * 25282 * <div class="alert alert-warning"> 25283 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 25284 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 25285 * the built-in validators (see the {@link guide/forms Forms guide}) 25286 * </div> 25287 * 25288 * @param {string} ngModel Assignable angular expression to data-bind to. 25289 * @param {string=} name Property name of the form under which the control is published. 25290 * @param {string=} required Sets `required` validation error key if the value is not entered. 25291 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25292 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25293 * `required` when you want to data-bind to the `required` attribute. 25294 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 25295 * minlength. 25296 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 25297 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 25298 * any length. 25299 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 25300 * that contains the regular expression body that will be converted to a regular expression 25301 * as in the ngPattern directive. 25302 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 25303 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 25304 * If the expression evaluates to a RegExp object, then this is used directly. 25305 * If the expression evaluates to a string, then it will be converted to a RegExp 25306 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 25307 * `new RegExp('^abc$')`.<br /> 25308 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 25309 * start at the index of the last search's match, thus not taking the whole input value into 25310 * account. 25311 * @param {string=} ngChange Angular expression to be executed when input changes due to user 25312 * interaction with the input element. 25313 * 25314 * @example 25315 <example name="url-input-directive" module="urlExample"> 25316 <file name="index.html"> 25317 <script> 25318 angular.module('urlExample', [])
25319 .controller('ExampleController', ['$scope', function($scope) { 25320 $scope.url = { 25321 text: 'http://google.com' 25322 }; 25323 }]); 25324 </script> 25325 <form name="myForm" ng-controller="ExampleController"> 25326 <label>URL: 25327 <input type="url" name="input" ng-model="url.text" required> 25328 <label> 25329 <div role="alert"> 25330 <span class="error" ng-show="myForm.input.$error.required"> 25331 Required!</span> 25332 <span class="error" ng-show="myForm.input.$error.url"> 25333 Not valid url!</span> 25334 </div> 25335 <tt>text = {{url.text}}</tt><br/> 25336 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25337 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25338 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25339 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25340 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 25341 </form> 25342 </file> 25343 <file name="protractor.js" type="protractor"> 25344 var text = element(by.binding('url.text')); 25345 var valid = element(by.binding('myForm.input.$valid')); 25346 var input = element(by.model('url.text')); 25347 25348 it('should initialize to model', function() { 25349 expect(text.getText()).toContain('http://google.com'); 25350 expect(valid.getText()).toContain('true'); 25351 }); 25352 25353 it('should be invalid if empty', function() { 25354 input.clear(); 25355 input.sendKeys(''); 25356 25357 expect(text.getText()).toEqual('text ='); 25358 expect(valid.getText()).toContain('false'); 25359 }); 25360 25361 it('should be invalid if not url', function() { 25362 input.clear(); 25363 input.sendKeys('box'); 25364 25365 expect(valid.getText()).toContain('false'); 25366 }); 25367 </file> 25368 </example> 25369 */ 25370 'url': urlInputType, 25371 25372 25373 /** 25374 * @ngdoc input 25375 * @name input[email] 25376 * 25377 * @description 25378 * Text input with email validation. Sets the `email` validation error key if not a valid email 25379 * address. 25380 * 25381 * <div class="alert alert-warning"> 25382 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 25383 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 25384 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 25385 * </div> 25386 * 25387 * @param {string} ngModel Assignable angular expression to data-bind to. 25388 * @param {string=} name Property name of the form under which the control is published. 25389 * @param {string=} required Sets `required` validation error key if the value is not entered. 25390 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25391 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25392 * `required` when you want to data-bind to the `required` attribute. 25393 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 25394 * minlength. 25395 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 25396 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 25397 * any length. 25398 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 25399 * that contains the regular expression body that will be converted to a regular expression 25400 * as in the ngPattern directive. 25401 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 25402 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 25403 * If the expression evaluates to a RegExp object, then this is used directly. 25404 * If the expression evaluates to a string, then it will be converted to a RegExp 25405 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 25406 * `new RegExp('^abc$')`.<br /> 25407 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 25408 * start at the index of the last search's match, thus not taking the whole input value into 25409 * account. 25410 * @param {string=} ngChange Angular expression to be executed when input changes due to user 25411 * interaction with the input element. 25412 * 25413 * @example 25414 <example name="email-input-directive" module="emailExample"> 25415 <file name="index.html"> 25416 <script> 25417 angular.module('emailExample', [])
25418 .controller('ExampleController', ['$scope', function($scope) { 25419 $scope.email = { 25420 text: '[email protected]' 25421 }; 25422 }]); 25423 </script> 25424 <form name="myForm" ng-controller="ExampleController"> 25425 <label>Email: 25426 <input type="email" name="input" ng-model="email.text" required> 25427 </label> 25428 <div role="alert"> 25429 <span class="error" ng-show="myForm.input.$error.required"> 25430 Required!</span> 25431 <span class="error" ng-show="myForm.input.$error.email"> 25432 Not valid email!</span> 25433 </div> 25434 <tt>text = {{email.text}}</tt><br/> 25435 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 25436 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 25437 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 25438 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 25439 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 25440 </form> 25441 </file> 25442 <file name="protractor.js" type="protractor"> 25443 var text = element(by.binding('email.text')); 25444 var valid = element(by.binding('myForm.input.$valid')); 25445 var input = element(by.model('email.text')); 25446 25447 it('should initialize to model', function() { 25448 expect(text.getText()).toContain('[email protected]'); 25449 expect(valid.getText()).toContain('true'); 25450 }); 25451 25452 it('should be invalid if empty', function() { 25453 input.clear(); 25454 input.sendKeys(''); 25455 expect(text.getText()).toEqual('text ='); 25456 expect(valid.getText()).toContain('false'); 25457 }); 25458 25459 it('should be invalid if not email', function() { 25460 input.clear(); 25461 input.sendKeys('xxx'); 25462 25463 expect(valid.getText()).toContain('false'); 25464 }); 25465 </file> 25466 </example> 25467 */ 25468 'email': emailInputType, 25469 25470 25471 /** 25472 * @ngdoc input 25473 * @name input[radio] 25474 * 25475 * @description 25476 * HTML radio button. 25477 * 25478 * @param {string} ngModel Assignable angular expression to data-bind to. 25479 * @param {string} value The value to which the `ngModel` expression should be set when selected. 25480 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 25481 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 25482 * @param {string=} name Property name of the form under which the control is published. 25483 * @param {string=} ngChange Angular expression to be executed when input changes due to user 25484 * interaction with the input element. 25485 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio 25486 * is selected. Should be used instead of the `value` attribute if you need 25487 * a non-string `ngModel` (`boolean`, `array`, ...). 25488 * 25489 * @example 25490 <example name="radio-input-directive" module="radioExample"> 25491 <file name="index.html"> 25492 <script> 25493 angular.module('radioExample', []) 25494 .controller('ExampleController', ['$scope', function($scope) { 25495 $scope.color = { 25496 name: 'blue' 25497 }; 25498 $scope.specialValue = { 25499 "id": "12345", 25500 "value": "green" 25501 }; 25502 }]); 25503 </script>
25504 <form name="myForm" ng-controller="ExampleController"> 25505 <label> 25506 <input type="radio" ng-model="color.name" value="red"> 25507 Red 25508 </label><br/> 25509 <label> 25510 <input type="radio" ng-model="color.name" ng-value="specialValue"> 25511 Green 25512 </label><br/> 25513 <label> 25514 <input type="radio" ng-model="color.name" value="blue"> 25515 Blue 25516 </label><br/> 25517 <tt>color = {{color.name | json}}</tt><br/> 25518 </form> 25519 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 25520 </file> 25521 <file name="protractor.js" type="protractor"> 25522 it('should change state', function() { 25523 var color = element(by.binding('color.name')); 25524 25525 expect(color.getText()).toContain('blue'); 25526 25527 element.all(by.model('color.name')).get(0).click(); 25528 25529 expect(color.getText()).toContain('red'); 25530 }); 25531 </file> 25532 </example> 25533 */ 25534 'radio': radioInputType, 25535 25536 25537 /** 25538 * @ngdoc input 25539 * @name input[checkbox] 25540 * 25541 * @description 25542 * HTML checkbox. 25543 * 25544 * @param {string} ngModel Assignable angular expression to data-bind to. 25545 * @param {string=} name Property name of the form under which the control is published. 25546 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 25547 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 25548 * @param {string=} ngChange Angular expression to be executed when input changes due to user 25549 * interaction with the input element. 25550 * 25551 * @example 25552 <example name="checkbox-input-directive" module="checkboxExample"> 25553 <file name="index.html"> 25554 <script> 25555 angular.module('checkboxExample', []) 25556 .controller('ExampleController', ['$scope', function($scope) {
25557 $scope.checkboxModel = { 25558 value1 : true, 25559 value2 : 'YES' 25560 }; 25561 }]); 25562 </script> 25563 <form name="myForm" ng-controller="ExampleController"> 25564 <label>Value1: 25565 <input type="checkbox" ng-model="checkboxModel.value1"> 25566 </label><br/> 25567 <label>Value2: 25568 <input type="checkbox" ng-model="checkboxModel.value2" 25569 ng-true-value="'YES'" ng-false-value="'NO'"> 25570 </label><br/> 25571 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 25572 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 25573 </form> 25574 </file> 25575 <file name="protractor.js" type="protractor"> 25576 it('should change state', function() { 25577 var value1 = element(by.binding('checkboxModel.value1')); 25578 var value2 = element(by.binding('checkboxModel.value2')); 25579 25580 expect(value1.getText()).toContain('true'); 25581 expect(value2.getText()).toContain('YES'); 25582 25583 element(by.model('checkboxModel.value1')).click(); 25584 element(by.model('checkboxModel.value2')).click(); 25585 25586 expect(value1.getText()).toContain('false'); 25587 expect(value2.getText()).toContain('NO'); 25588 }); 25589 </file> 25590 </example> 25591 */ 25592 'checkbox': checkboxInputType, 25593 25594 'hidden': noop, 25595 'button': noop, 25596 'submit': noop, 25597 'reset': noop, 25598 'file': noop 25599}; 25600 25601function stringBasedInputType(ctrl) { 25602 ctrl.$formatters.push(function(value) { 25603 return ctrl.$isEmpty(value) ? value : value.toString(); 25604 }); 25605} 25606 25607function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 25608 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 25609 stringBasedInputType(ctrl); 25610} 25611 25612function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 25613 var type = lowercase(element[0].type); 25614 25615 // In composition mode, users are still inputing intermediate text buffer, 25616 // hold the listener until composition is done. 25617 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 25618 if (!$sniffer.android) { 25619 var composing = false; 25620 25621 element.on('compositionstart', function() { 25622 composing = true; 25623 }); 25624 25625 element.on('compositionend', function() { 25626 composing = false; 25627 listener(); 25628 }); 25629 } 25630 25631 var timeout; 25632 25633 var listener = function(ev) { 25634 if (timeout) { 25635 $browser.defer.cancel(timeout); 25636 timeout = null; 25637 } 25638 if (composing) return; 25639 var value = element.val(), 25640 event = ev && ev.type; 25641 25642 // By default we will trim the value 25643 // If the attribute ng-trim exists we will avoid trimming 25644 // If input type is 'password', the value is never trimmed 25645 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 25646 value = trim(value); 25647 } 25648 25649 // If a control is suffering from bad input (due to native validators), browsers discard its 25650 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 25651 // control's value is the same empty value twice in a row. 25652 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 25653 ctrl.$setViewValue(value, event); 25654 } 25655 }; 25656 25657 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 25658 // input event on backspace, delete or cut 25659 if ($sniffer.hasEvent('input')) { 25660 element.on('input', listener); 25661 } else { 25662 var deferListener = function(ev, input, origValue) { 25663 if (!timeout) { 25664 timeout = $browser.defer(function() { 25665 timeout = null; 25666 if (!input || input.value !== origValue) { 25667 listener(ev); 25668 } 25669 }); 25670 } 25671 }; 25672 25673 element.on('keydown', function(event) { 25674 var key = event.keyCode; 25675 25676 // ignore 25677 // command modifiers arrows 25678 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 25679 25680 deferListener(event, this, this.value); 25681 }); 25682 25683 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 25684 if ($sniffer.hasEvent('paste')) { 25685 element.on('paste cut', deferListener); 25686 } 25687 } 25688 25689 // if user paste into input using mouse on older browser 25690 // or form autocomplete on newer browser, we need "change" event to catch it 25691 element.on('change', listener); 25692 25693 // Some native input types (date-family) have the ability to change validity without 25694 // firing any input/change events. 25695 // For these event types, when native validators are present and the browser supports the type, 25696 // check for validity changes on various DOM events. 25697 if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { 25698 element.on(PARTIAL_VALIDATION_EVENTS, function(ev) { 25699 if (!timeout) {
25700 var validity = this[VALIDITY_STATE_PROPERTY]; 25701 var origBadInput = validity.badInput; 25702 var origTypeMismatch = validity.typeMismatch; 25703 timeout = $browser.defer(function() { 25704 timeout = null; 25705 if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) { 25706 listener(ev); 25707 } 25708 }); 25709 } 25710 }); 25711 } 25712 25713 ctrl.$render = function() { 25714 // Workaround for Firefox validation #12102. 25715 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 25716 if (element.val() !== value) { 25717 element.val(value); 25718 } 25719 }; 25720} 25721 25722function weekParser(isoWeek, existingDate) { 25723 if (isDate(isoWeek)) { 25724 return isoWeek; 25725 } 25726 25727 if (isString(isoWeek)) { 25728 WEEK_REGEXP.lastIndex = 0; 25729 var parts = WEEK_REGEXP.exec(isoWeek); 25730 if (parts) { 25731 var year = +parts[1], 25732 week = +parts[2], 25733 hours = 0, 25734 minutes = 0, 25735 seconds = 0, 25736 milliseconds = 0, 25737 firstThurs = getFirstThursdayOfYear(year), 25738 addDays = (week - 1) * 7; 25739 25740 if (existingDate) { 25741 hours = existingDate.getHours(); 25742 minutes = existingDate.getMinutes(); 25743 seconds = existingDate.getSeconds(); 25744 milliseconds = existingDate.getMilliseconds(); 25745 } 25746 25747 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 25748 } 25749 } 25750 25751 return NaN; 25752} 25753 25754function createDateParser(regexp, mapping) { 25755 return function(iso, date) { 25756 var parts, map; 25757 25758 if (isDate(iso)) { 25759 return iso; 25760 } 25761 25762 if (isString(iso)) { 25763 // When a date is JSON'ified to wraps itself inside of an extra 25764 // set of double quotes. This makes the date parsing code unable 25765 // to match the date string and parse it as a date. 25766 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 25767 iso = iso.substring(1, iso.length - 1); 25768 } 25769 if (ISO_DATE_REGEXP.test(iso)) { 25770 return new Date(iso); 25771 } 25772 regexp.lastIndex = 0; 25773 parts = regexp.exec(iso); 25774 25775 if (parts) { 25776 parts.shift(); 25777 if (date) { 25778 map = { 25779 yyyy: date.getFullYear(), 25780 MM: date.getMonth() + 1, 25781 dd: date.getDate(), 25782 HH: date.getHours(), 25783 mm: date.getMinutes(), 25784 ss: date.getSeconds(), 25785 sss: date.getMilliseconds() / 1000 25786 }; 25787 } else { 25788 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 25789 } 25790 25791 forEach(parts, function(part, index) { 25792 if (index < mapping.length) { 25793 map[mapping[index]] = +part; 25794 } 25795 }); 25796 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 25797 } 25798 } 25799 25800 return NaN; 25801 }; 25802} 25803 25804function createDateInputType(type, regexp, parseDate, format) { 25805 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 25806 badInputChecker(scope, element, attr, ctrl); 25807 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 25808 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 25809 var previousDate; 25810 25811 ctrl.$$parserName = type; 25812 ctrl.$parsers.push(function(value) { 25813 if (ctrl.$isEmpty(value)) return null; 25814 if (regexp.test(value)) { 25815 // Note: We cannot read ctrl.$modelValue, as there might be a different 25816 // parser/formatter in the processing chain so that the model 25817 // contains some different data format! 25818 var parsedDate = parseDate(value, previousDate); 25819 if (timezone) { 25820 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 25821 } 25822 return parsedDate; 25823 } 25824 return undefined; 25825 }); 25826 25827 ctrl.$formatters.push(function(value) { 25828 if (value && !isDate(value)) { 25829 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 25830 } 25831 if (isValidDate(value)) { 25832 previousDate = value; 25833 if (previousDate && timezone) { 25834 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 25835 } 25836 return $filter('date')(value, format, timezone); 25837 } else { 25838 previousDate = null; 25839 return ''; 25840 } 25841 }); 25842 25843 if (isDefined(attr.min) || attr.ngMin) { 25844 var minVal; 25845 ctrl.$validators.min = function(value) { 25846 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 25847 }; 25848 attr.$observe('min', function(val) { 25849 minVal = parseObservedDateValue(val); 25850 ctrl.$validate(); 25851 }); 25852 } 25853 25854 if (isDefined(attr.max) || attr.ngMax) { 25855 var maxVal; 25856 ctrl.$validators.max = function(value) { 25857 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 25858 }; 25859 attr.$observe('max', function(val) { 25860 maxVal = parseObservedDateValue(val); 25861 ctrl.$validate(); 25862 }); 25863 } 25864 25865 function isValidDate(value) { 25866 // Invalid Date: getTime() returns NaN 25867 return value && !(value.getTime && value.getTime() !== value.getTime()); 25868 } 25869 25870 function parseObservedDateValue(val) { 25871 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; 25872 } 25873 }; 25874} 25875 25876function badInputChecker(scope, element, attr, ctrl) { 25877 var node = element[0]; 25878 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 25879 if (nativeValidation) { 25880 ctrl.$parsers.push(function(value) {
25881 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 25882 return validity.badInput || validity.typeMismatch ? undefined : value; 25883 }); 25884 } 25885} 25886 25887function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 25888 badInputChecker(scope, element, attr, ctrl); 25889 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 25890 25891 ctrl.$$parserName = 'number'; 25892 ctrl.$parsers.push(function(value) { 25893 if (ctrl.$isEmpty(value)) return null; 25894 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 25895 return undefined; 25896 }); 25897 25898 ctrl.$formatters.push(function(value) { 25899 if (!ctrl.$isEmpty(value)) { 25900 if (!isNumber(value)) { 25901 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 25902 } 25903 value = value.toString(); 25904 } 25905 return value; 25906 }); 25907 25908 if (isDefined(attr.min) || attr.ngMin) { 25909 var minVal; 25910 ctrl.$validators.min = function(value) { 25911 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 25912 }; 25913 25914 attr.$observe('min', function(val) { 25915 if (isDefined(val) && !isNumber(val)) { 25916 val = parseFloat(val, 10); 25917 } 25918 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 25919 // TODO(matsko): implement validateLater to reduce number of validations 25920 ctrl.$validate(); 25921 }); 25922 } 25923 25924 if (isDefined(attr.max) || attr.ngMax) { 25925 var maxVal; 25926 ctrl.$validators.max = function(value) { 25927 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 25928 }; 25929 25930 attr.$observe('max', function(val) { 25931 if (isDefined(val) && !isNumber(val)) { 25932 val = parseFloat(val, 10); 25933 } 25934 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 25935 // TODO(matsko): implement validateLater to reduce number of validations 25936 ctrl.$validate(); 25937 }); 25938 } 25939} 25940 25941function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 25942 // Note: no badInputChecker here by purpose as `url` is only a validation 25943 // in browsers, i.e. we can always read out input.value even if it is not valid! 25944 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 25945 stringBasedInputType(ctrl); 25946 25947 ctrl.$$parserName = 'url'; 25948 ctrl.$validators.url = function(modelValue, viewValue) { 25949 var value = modelValue || viewValue; 25950 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 25951 }; 25952} 25953 25954function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 25955 // Note: no badInputChecker here by purpose as `url` is only a validation 25956 // in browsers, i.e. we can always read out input.value even if it is not valid! 25957 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 25958 stringBasedInputType(ctrl); 25959 25960 ctrl.$$parserName = 'email'; 25961 ctrl.$validators.email = function(modelValue, viewValue) { 25962 var value = modelValue || viewValue; 25963 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 25964 }; 25965} 25966 25967function radioInputType(scope, element, attr, ctrl) { 25968 // make the name unique, if not defined 25969 if (isUndefined(attr.name)) { 25970 element.attr('name', nextUid()); 25971 } 25972 25973 var listener = function(ev) { 25974 if (element[0].checked) { 25975 ctrl.$setViewValue(attr.value, ev && ev.type); 25976 } 25977 }; 25978 25979 element.on('click', listener); 25980 25981 ctrl.$render = function() { 25982 var value = attr.value; 25983 element[0].checked = (value == ctrl.$viewValue); 25984 }; 25985 25986 attr.$observe('value', ctrl.$render); 25987} 25988 25989function parseConstantExpr($parse, context, name, expression, fallback) { 25990 var parseFn; 25991 if (isDefined(expression)) { 25992 parseFn = $parse(expression); 25993 if (!parseFn.constant) { 25994 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 25995 '`{1}`.', name, expression); 25996 } 25997 return parseFn(context); 25998 } 25999 return fallback; 26000} 26001 26002function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 26003 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 26004 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 26005 26006 var listener = function(ev) { 26007 ctrl.$setViewValue(element[0].checked, ev && ev.type); 26008 }; 26009 26010 element.on('click', listener); 26011 26012 ctrl.$render = function() { 26013 element[0].checked = ctrl.$viewValue; 26014 }; 26015
26016 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 26017 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 26018 // it to a boolean. 26019 ctrl.$isEmpty = function(value) { 26020 return value === false; 26021 }; 26022 26023 ctrl.$formatters.push(function(value) { 26024 return equals(value, trueValue); 26025 }); 26026 26027 ctrl.$parsers.push(function(value) { 26028 return value ? trueValue : falseValue; 26029 }); 26030} 26031 26032 26033/** 26034 * @ngdoc directive 26035 * @name textarea 26036 * @restrict E 26037 * 26038 * @description 26039 * HTML textarea element control with angular data-binding. The data-binding and validation 26040 * properties of this element are exactly the same as those of the 26041 * {@link ng.directive:input input element}. 26042 * 26043 * @param {string} ngModel Assignable angular expression to data-bind to. 26044 * @param {string=} name Property name of the form under which the control is published. 26045 * @param {string=} required Sets `required` validation error key if the value is not entered. 26046 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26047 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26048 * `required` when you want to data-bind to the `required` attribute. 26049 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26050 * minlength. 26051 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26052 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 26053 * length. 26054 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26055 * does not match a RegExp found by evaluating the Angular expression given in the attribute value. 26056 * If the expression evaluates to a RegExp object, then this is used directly. 26057 * If the expression evaluates to a string, then it will be converted to a RegExp 26058 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26059 * `new RegExp('^abc$')`.<br /> 26060 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26061 * start at the index of the last search's match, thus not taking the whole input value into 26062 * account. 26063 * @param {string=} ngChange Angular expression to be executed when input changes due to user 26064 * interaction with the input element. 26065 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 26066 */ 26067 26068 26069/** 26070 * @ngdoc directive 26071 * @name input 26072 * @restrict E 26073 * 26074 * @description 26075 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 26076 * input state control, and validation. 26077 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 26078 * 26079 * <div class="alert alert-warning"> 26080 * **Note:** Not every feature offered is available for all input types. 26081 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 26082 * </div> 26083 * 26084 * @param {string} ngModel Assignable angular expression to data-bind to. 26085 * @param {string=} name Property name of the form under which the control is published. 26086 * @param {string=} required Sets `required` validation error key if the value is not entered. 26087 * @param {boolean=} ngRequired Sets `required` attribute if set to true 26088 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 26089 * minlength. 26090 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 26091 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 26092 * length. 26093 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue} 26094 * value does not match a RegExp found by evaluating the Angular expression given in the attribute value. 26095 * If the expression evaluates to a RegExp object, then this is used directly. 26096 * If the expression evaluates to a string, then it will be converted to a RegExp 26097 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 26098 * `new RegExp('^abc$')`.<br /> 26099 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 26100 * start at the index of the last search's match, thus not taking the whole input value into 26101 * account. 26102 * @param {string=} ngChange Angular expression to be executed when input changes due to user 26103 * interaction with the input element. 26104 * @param {boolean=} [ngTrim=true] If set to false Angular
vendor: 4,842 bytes, lines 26104-26222
26104 will not automatically trim the input. 26105 * This parameter is ignored for input[type=password] controls, which will never trim the 26106 * input. 26107 * 26108 * @example 26109 <example name="input-directive" module="inputExample"> 26110 <file name="index.html"> 26111 <script> 26112 angular.module('inputExample', []) 26113 .controller('ExampleController', ['$scope', function($scope) { 26114 $scope.user = {name: 'guest', last: 'visitor'}; 26115 }]); 26116 </script> 26117 <div ng-controller="ExampleController"> 26118 <form name="myForm"> 26119 <label> 26120 User name: 26121 <input type="text" name="userName" ng-model="user.name" required> 26122 </label> 26123 <div role="alert"> 26124 <span class="error" ng-show="myForm.userName.$error.required"> 26125 Required!</span> 26126 </div> 26127 <label> 26128 Last name: 26129 <input type="text" name="lastName" ng-model="user.last" 26130 ng-minlength="3" ng-maxlength="10"> 26131 </label> 26132 <div role="alert"> 26133 <span class="error" ng-show="myForm.lastName.$error.minlength"> 26134 Too short!</span> 26135 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 26136 Too long!</span> 26137 </div> 26138 </form> 26139 <hr> 26140 <tt>user = {{user}}</tt><br/> 26141 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 26142 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 26143 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 26144 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 26145 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 26146 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 26147 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 26148 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 26149 </div> 26150 </file> 26151 <file name="protractor.js" type="protractor"> 26152 var user = element(by.exactBinding('user')); 26153 var userNameValid = element(by.binding('myForm.userName.$valid')); 26154 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 26155 var lastNameError = element(by.binding('myForm.lastName.$error')); 26156 var formValid = element(by.binding('myForm.$valid')); 26157 var userNameInput = element(by.model('user.name')); 26158 var userLastInput = element(by.model('user.last')); 26159 26160 it('should initialize to model', function() { 26161 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 26162 expect(userNameValid.getText()).toContain('true'); 26163 expect(formValid.getText()).toContain('true'); 26164 }); 26165 26166 it('should be invalid if empty when required', function() { 26167 userNameInput.clear(); 26168 userNameInput.sendKeys(''); 26169 26170 expect(user.getText()).toContain('{"last":"visitor"}'); 26171 expect(userNameValid.getText()).toContain('false'); 26172 expect(formValid.getText()).toContain('false'); 26173 }); 26174 26175 it('should be valid if empty when min length is set', function() { 26176 userLastInput.clear(); 26177 userLastInput.sendKeys(''); 26178 26179 expect(user.getText()).toContain('{"name":"guest","last":""}'); 26180 expect(lastNameValid.getText()).toContain('true'); 26181 expect(formValid.getText()).toContain('true'); 26182 }); 26183 26184 it('should be invalid if less than required min length', function() { 26185 userLastInput.clear(); 26186 userLastInput.sendKeys('xx'); 26187 26188 expect(user.getText()).toContain('{"name":"guest"}'); 26189 expect(lastNameValid.getText()).toContain('false'); 26190 expect(lastNameError.getText()).toContain('minlength'); 26191 expect(formValid.getText()).toContain('false'); 26192 }); 26193 26194 it('should be invalid if longer than max length', function() { 26195 userLastInput.clear(); 26196 userLastInput.sendKeys('some ridiculously long name'); 26197 26198 expect(user.getText()).toContain('{"name":"guest"}'); 26199 expect(lastNameValid.getText()).toContain('false'); 26200 expect(lastNameError.getText()).toContain('maxlength'); 26201 expect(formValid.getText()).toContain('false'); 26202 }); 26203 </file> 26204 </example> 26205 */ 26206var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 26207 function($browser, $sniffer, $filter, $parse) { 26208 return { 26209 restrict: 'E', 26210 require: ['?ngModel'], 26211 link: { 26212 pre: function(scope, element, attr, ctrls) { 26213 if (ctrls[0]) { 26214 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 26215 $browser, $filter, $parse); 26216 } 26217 } 26218 } 26219 }; 26220}]; 26221 26222
26223 26224var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 26225/** 26226 * @ngdoc directive 26227 * @name ngValue 26228 * 26229 * @description 26230 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 26231 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 26232 * the bound value. 26233 * 26234 * `ngValue` is useful when dynamically generating lists of radio buttons using 26235 * {@link ngRepeat `ngRepeat`}, as shown below. 26236 * 26237 * Likewise, `ngValue` can be used to generate `<option>` elements for 26238 * the {@link select `select`} element. In that case however, only strings are supported 26239 * for the `value `attribute, so the resulting `ngModel` will always be a string. 26240 * Support for `select` models with non-string values is available via `ngOptions`. 26241 * 26242 * @element input 26243 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 26244 * of the `input` element 26245 * 26246 * @example 26247 <example name="ngValue-directive" module="valueExample"> 26248 <file name="index.html"> 26249 <script> 26250 angular.module('valueExample', []) 26251 .controller('ExampleController', ['$scope', function($scope) { 26252 $scope.names = ['pizza', 'unicorns', 'robots']; 26253 $scope.my = { favorite: 'unicorns' }; 26254 }]); 26255 </script>
26256 <form ng-controller="ExampleController"> 26257 <h2>Which is your favorite?</h2> 26258 <label ng-repeat="name in names" for="{{name}}"> 26259 {{name}} 26260 <input type="radio" 26261 ng-model="my.favorite" 26262 ng-value="name" 26263 id="{{name}}" 26264 name="favorite"> 26265 </label> 26266 <div>You chose {{my.favorite}}</div> 26267 </form> 26268 </file> 26269 <file name="protractor.js" type="protractor"> 26270 var favorite = element(by.binding('my.favorite')); 26271 26272 it('should initialize to model', function() { 26273 expect(favorite.getText()).toContain('unicorns'); 26274 }); 26275 it('should bind the values to the inputs', function() { 26276 element.all(by.model('my.favorite')).get(0).click(); 26277 expect(favorite.getText()).toContain('pizza'); 26278 }); 26279 </file> 26280 </example> 26281 */ 26282var ngValueDirective = function() { 26283 return { 26284 restrict: 'A', 26285 priority: 100, 26286 compile: function(tpl, tplAttr) { 26287 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 26288 return function ngValueConstantLink(scope, elm, attr) { 26289 attr.$set('value', scope.$eval(attr.ngValue)); 26290 }; 26291 } else { 26292 return function ngValueLink(scope, elm, attr) { 26293 scope.$watch(attr.ngValue, function valueWatchAction(value) { 26294 attr.$set('value', value); 26295 }); 26296 }; 26297 } 26298 } 26299 }; 26300}; 26301 26302/** 26303 * @ngdoc directive 26304 * @name ngBind 26305 * @restrict AC 26306 * 26307 * @description 26308 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 26309 * with the value of a given expression, and to update the text content when the value of that 26310 * expression changes. 26311 * 26312 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 26313 * `{{ expression }}` which is similar but less verbose. 26314 * 26315 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 26316 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 26317 * element attribute, it makes the bindings invisible to the user while the page is loading. 26318 * 26319 * An alternative solution to this problem would be using the 26320 * {@link ng.directive:ngCloak ngCloak} directive. 26321 * 26322 * 26323 * @element ANY 26324 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 26325 * 26326 * @example 26327 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 26328 <example module="bindExample"> 26329 <file name="index.html"> 26330 <script> 26331 angular.module('bindExample', []) 26332 .controller('ExampleController', ['$scope', function($scope) { 26333 $scope.name = 'Whirled'; 26334 }]); 26335 </script>
26336 <div ng-controller="ExampleController"> 26337 <label>Enter name: <input type="text" ng-model="name"></label><br> 26338 Hello <span ng-bind="name"></span>! 26339 </div> 26340 </file> 26341 <file name="protractor.js" type="protractor"> 26342 it('should check ng-bind', function() { 26343 var nameInput = element(by.model('name')); 26344 26345 expect(element(by.binding('name')).getText()).toBe('Whirled'); 26346 nameInput.clear(); 26347 nameInput.sendKeys('world'); 26348 expect(element(by.binding('name')).getText()).toBe('world'); 26349 }); 26350 </file> 26351 </example> 26352 */ 26353var ngBindDirective = ['$compile', function($compile) { 26354 return { 26355 restrict: 'AC', 26356 compile: function ngBindCompile(templateElement) { 26357 $compile.$$addBindingClass(templateElement); 26358 return function ngBindLink(scope, element, attr) { 26359 $compile.$$addBindingInfo(element, attr.ngBind); 26360 element = element[0]; 26361 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 26362 element.textContent = isUndefined(value) ? '' : value; 26363 }); 26364 }; 26365 } 26366 }; 26367}]; 26368 26369 26370/** 26371 * @ngdoc directive 26372 * @name ngBindTemplate 26373 * 26374 * @description 26375 * The `ngBindTemplate` directive specifies that the element 26376 * text content should be replaced with the interpolation of the template 26377 * in the `ngBindTemplate` attribute. 26378 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 26379 * expressions. This directive is needed since some HTML elements 26380 * (such as TITLE and OPTION) cannot contain SPAN elements. 26381 * 26382 * @element ANY 26383 * @param {string} ngBindTemplate template of form 26384 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 26385 * 26386 * @example 26387 * Try it here: enter text in text box and watch the greeting change. 26388 <example module="bindExample"> 26389 <file name="index.html"> 26390 <script> 26391 angular.module('bindExample', []) 26392 .controller('ExampleController', ['$scope', function($scope) { 26393 $scope.salutation = 'Hello'; 26394 $scope.name = 'World'; 26395 }]); 26396 </script>
26397 <div ng-controller="ExampleController"> 26398 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 26399 <label>Name: <input type="text" ng-model="name"></label><br> 26400 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 26401 </div> 26402 </file> 26403 <file name="protractor.js" type="protractor"> 26404 it('should check ng-bind', function() { 26405 var salutationElem = element(by.binding('salutation')); 26406 var salutationInput = element(by.model('salutation')); 26407 var nameInput = element(by.model('name')); 26408 26409 expect(salutationElem.getText()).toBe('Hello World!'); 26410 26411 salutationInput.clear(); 26412 salutationInput.sendKeys('Greetings'); 26413 nameInput.clear(); 26414 nameInput.sendKeys('user'); 26415 26416 expect(salutationElem.getText()).toBe('Greetings user!'); 26417 }); 26418 </file> 26419 </example> 26420 */ 26421var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 26422 return { 26423 compile: function ngBindTemplateCompile(templateElement) { 26424 $compile.$$addBindingClass(templateElement); 26425 return function ngBindTemplateLink(scope, element, attr) { 26426 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 26427 $compile.$$addBindingInfo(element, interpolateFn.expressions); 26428 element = element[0]; 26429 attr.$observe('ngBindTemplate', function(value) { 26430 element.textContent = isUndefined(value) ? '' : value; 26431 }); 26432 }; 26433 } 26434 }; 26435}]; 26436 26437 26438/** 26439 * @ngdoc directive 26440 * @name ngBindHtml 26441 * 26442 * @description 26443 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 26444 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 26445 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 26446 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 26447 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 26448 * 26449 * You may also bypass sanitization for values you know are safe. To do so, bind to 26450 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 26451 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 26452 * 26453 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 26454 * will have an exception (instead of an exploit.) 26455 * 26456 * @element ANY 26457 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 26458 * 26459 * @example 26460 26461 <example module="bindHtmlExample" deps="angular-sanitize.js"> 26462 <file name="index.html">
26463 <div ng-controller="ExampleController"> 26464 <p ng-bind-html="myHTML"></p> 26465 </div> 26466 </file> 26467 26468 <file name="script.js"> 26469 angular.module('bindHtmlExample', ['ngSanitize']) 26470 .controller('ExampleController', ['$scope', function($scope) { 26471 $scope.myHTML = 26472 'I am an <code>HTML</code>string with ' + 26473 '<a href="#">links!</a> and other <em>stuff</em>'; 26474 }]); 26475 </file> 26476 26477 <file name="protractor.js" type="protractor"> 26478 it('should check ng-bind-html', function() { 26479 expect(element(by.binding('myHTML')).getText()).toBe( 26480 'I am an HTMLstring with links! and other stuff'); 26481 }); 26482 </file> 26483 </example> 26484 */ 26485var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 26486 return { 26487 restrict: 'A', 26488 compile: function ngBindHtmlCompile(tElement, tAttrs) { 26489 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 26490 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 26491 return (value || '').toString(); 26492 }); 26493 $compile.$$addBindingClass(tElement); 26494 26495 return function ngBindHtmlLink(scope, element, attr) { 26496 $compile.$$addBindingInfo(element, attr.ngBindHtml); 26497 26498 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 26499 // we re-evaluate the expr because we want a TrustedValueHolderType 26500 // for $sce, not a string 26501 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 26502 }); 26503 }; 26504 } 26505 }; 26506}]; 26507 26508/** 26509 * @ngdoc directive 26510 * @name ngChange 26511 * 26512 * @description 26513 * Evaluate the given expression when the user changes the input. 26514 * The expression is evaluated immediately, unlike the JavaScript onchange event 26515 * which only triggers at the end of a change (usually, when the user leaves the 26516 * form element or presses the return key). 26517 * 26518 * The `ngChange` expression is only evaluated when a change in the input value causes 26519 * a new value to be committed to the model. 26520 * 26521 * It will not be evaluated: 26522 * * if the value returned from the `$parsers` transformation pipeline has not changed 26523 * * if the input has continued to be invalid since the model will stay `null` 26524 * * if the model is changed programmatically and not by a change to the input value 26525 * 26526 * 26527 * Note, this directive requires `ngModel` to be present. 26528 * 26529 * @element input 26530 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 26531 * in input value. 26532 * 26533 * @example 26534 * <example name="ngChange-directive" module="changeExample"> 26535 * <file name="index.html"> 26536 * <script> 26537 * angular.module('changeExample', []) 26538 * .controller('ExampleController', ['$scope', function($scope) { 26539 * $scope.counter = 0; 26540 * $scope.change = function() { 26541 * $scope.counter++; 26542 * }; 26543 * }]); 26544 * </script>
26545 * <div ng-controller="ExampleController"> 26546 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 26547 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 26548 * <label for="ng-change-example2">Confirmed</label><br /> 26549 * <tt>debug = {{confirmed}}</tt><br/> 26550 * <tt>counter = {{counter}}</tt><br/> 26551 * </div> 26552 * </file> 26553 * <file name="protractor.js" type="protractor"> 26554 * var counter = element(by.binding('counter')); 26555 * var debug = element(by.binding('confirmed')); 26556 * 26557 * it('should evaluate the expression if changing from view', function() { 26558 * expect(counter.getText()).toContain('0'); 26559 * 26560 * element(by.id('ng-change-example1')).click(); 26561 * 26562 * expect(counter.getText()).toContain('1'); 26563 * expect(debug.getText()).toContain('true'); 26564 * }); 26565 * 26566 * it('should not evaluate the expression if changing from model', function() { 26567 * element(by.id('ng-change-example2')).click(); 26568 26569 * expect(counter.getText()).toContain('0'); 26570 * expect(debug.getText()).toContain('true'); 26571 * }); 26572 * </file> 26573 * </example> 26574 */ 26575var ngChangeDirective = valueFn({ 26576 restrict: 'A', 26577 require: 'ngModel', 26578 link: function(scope, element, attr, ctrl) { 26579 ctrl.$viewChangeListeners.push(function() { 26580 scope.$eval(attr.ngChange); 26581 }); 26582 } 26583}); 26584 26585function classDirective(name, selector) { 26586 name = 'ngClass' + name; 26587 return ['$animate', function($animate) { 26588 return { 26589 restrict: 'AC', 26590 link: function(scope, element, attr) { 26591 var oldVal; 26592 26593 scope.$watch(attr[name], ngClassWatchAction, true); 26594 26595 attr.$observe('class', function(value) { 26596 ngClassWatchAction(scope.$eval(attr[name])); 26597 }); 26598 26599 26600 if (name !== 'ngClass') { 26601 scope.$watch('$index', function($index, old$index) { 26602 // jshint bitwise: false 26603 var mod = $index & 1; 26604 if (mod !== (old$index & 1)) { 26605 var classes = arrayClasses(scope.$eval(attr[name])); 26606 mod === selector ? 26607 addClasses(classes) : 26608 removeClasses(classes); 26609 } 26610 }); 26611 } 26612 26613 function addClasses(classes) { 26614 var newClasses = digestClassCounts(classes, 1); 26615 attr.$addClass(newClasses); 26616 } 26617 26618 function removeClasses(classes) { 26619 var newClasses = digestClassCounts(classes, -1); 26620 attr.$removeClass(newClasses); 26621 } 26622 26623 function digestClassCounts(classes, count) { 26624 // Use createMap() to prevent class assumptions involving property 26625 // names in Object.prototype 26626 var classCounts = element.data('$classCounts') || createMap(); 26627 var classesToUpdate = []; 26628 forEach(classes, function(className) { 26629 if (count > 0 || classCounts[className]) { 26630 classCounts[className] = (classCounts[className] || 0) + count; 26631 if (classCounts[className] === +(count > 0)) { 26632 classesToUpdate.push(className); 26633 } 26634 } 26635 }); 26636 element.data('$classCounts', classCounts); 26637 return classesToUpdate.join(' '); 26638 } 26639 26640 function updateClasses(oldClasses, newClasses) { 26641 var toAdd = arrayDifference(newClasses, oldClasses); 26642 var toRemove = arrayDifference(oldClasses, newClasses); 26643 toAdd = digestClassCounts(toAdd, 1); 26644 toRemove = digestClassCounts(toRemove, -1); 26645 if (toAdd && toAdd.length) { 26646 $animate.addClass(element, toAdd); 26647 } 26648 if (toRemove && toRemove.length) { 26649 $animate.removeClass(element, toRemove); 26650 } 26651 } 26652 26653 function ngClassWatchAction(newVal) { 26654 if (selector === true || scope.$index % 2 === selector) { 26655 var newClasses = arrayClasses(newVal || []); 26656 if (!oldVal) { 26657 addClasses(newClasses); 26658 } else if (!equals(newVal,oldVal)) { 26659 var oldClasses = arrayClasses(oldVal);
26660 updateClasses(oldClasses, newClasses); 26661 } 26662 } 26663 if (isArray(newVal)) { 26664 oldVal = newVal.map(function(v) { return shallowCopy(v); }); 26665 } else { 26666 oldVal = shallowCopy(newVal); 26667 } 26668 } 26669 } 26670 }; 26671 26672 function arrayDifference(tokens1, tokens2) { 26673 var values = []; 26674 26675 outer: 26676 for (var i = 0; i < tokens1.length; i++) { 26677 var token = tokens1[i]; 26678 for (var j = 0; j < tokens2.length; j++) { 26679 if (token == tokens2[j]) continue outer; 26680 } 26681 values.push(token); 26682 } 26683 return values; 26684 } 26685 26686 function arrayClasses(classVal) { 26687 var classes = []; 26688 if (isArray(classVal)) { 26689 forEach(classVal, function(v) { 26690 classes = classes.concat(arrayClasses(v)); 26691 }); 26692 return classes; 26693 } else if (isString(classVal)) { 26694 return classVal.split(' '); 26695 } else if (isObject(classVal)) { 26696 forEach(classVal, function(v, k) { 26697 if (v) { 26698 classes = classes.concat(k.split(' ')); 26699 } 26700 }); 26701 return classes; 26702 } 26703 return classVal; 26704 } 26705 }]; 26706} 26707 26708/** 26709 * @ngdoc directive 26710 * @name ngClass 26711 * @restrict AC 26712 * 26713 * @description 26714 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 26715 * an expression that represents all classes to be added. 26716 * 26717 * The directive operates in three different ways, depending on which of three types the expression 26718 * evaluates to: 26719 * 26720 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 26721 * names. 26722 * 26723 * 2. If the expression evaluates to an object, then for each key-value pair of the 26724 * object with a truthy value the corresponding key is used as a class name. 26725 * 26726 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 26727 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 26728 * to give you more control over what CSS classes appear. See the code below for an example of this. 26729 * 26730 * 26731 * The directive won't add duplicate classes if a particular class was already set. 26732 * 26733 * When the expression changes, the previously added classes are removed and only then are the 26734 * new classes added. 26735 * 26736 * @animations 26737 * | Animation | Occurs | 26738 * |----------------------------------|-------------------------------------| 26739 * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element | 26740 * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element | 26741 * 26742 * @element ANY 26743 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 26744 * of the evaluation can be a string representing space delimited class 26745 * names, an array, or a map of class names to boolean values. In the case of a map, the 26746 * names of the properties whose values are truthy will be added as css classes to the 26747 * element. 26748 * 26749 * @example Example that demonstrates basic bindings via ngClass directive. 26750 <example> 26751 <file name="index.html"> 26752 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 26753 <label> 26754 <input type="checkbox" ng-model="deleted"> 26755 deleted (apply "strike" class) 26756 </label><br> 26757 <label> 26758 <input type="checkbox" ng-model="important"> 26759 important (apply "bold" class) 26760 </label><br> 26761 <label> 26762 <input type="checkbox" ng-model="error"> 26763 error (apply "has-error" class) 26764 </label> 26765 <hr> 26766 <p ng-class="style">Using String Syntax</p> 26767 <input type="text" ng-model="style" 26768 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 26769 <hr> 26770 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 26771 <input ng-model="style1" 26772 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 26773 <input ng-model="style2" 26774 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 26775 <input ng-model="style3" 26776 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 26777 <hr> 26778 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 26779 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 26780 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 26781 </file> 26782 <file name="style.css"> 26783 .strike { 26784 text-decoration: line-through; 26785 } 26786 .bold { 26787 font-weight: bold; 26788 } 26789 .red { 26790 color: red; 26791 } 26792 .has-error { 26793 color: red; 26794 background-color: yellow; 26795 } 26796 .orange { 26797 color: orange; 26798 } 26799 </file> 26800 <file name="protractor.js" type="protractor"> 26801 var ps = element.all(by.css('p')); 26802 26803 it('should let you toggle the class', function() { 26804 26805 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 26806 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 26807
26808 element(by.model('important')).click(); 26809 expect(ps.first().getAttribute('class')).toMatch(/bold/); 26810 26811 element(by.model('error')).click(); 26812 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 26813 }); 26814 26815 it('should let you toggle string example', function() { 26816 expect(ps.get(1).getAttribute('class')).toBe(''); 26817 element(by.model('style')).clear(); 26818 element(by.model('style')).sendKeys('red'); 26819 expect(ps.get(1).getAttribute('class')).toBe('red'); 26820 }); 26821 26822 it('array example should have 3 classes', function() { 26823 expect(ps.get(2).getAttribute('class')).toBe(''); 26824 element(by.model('style1')).sendKeys('bold'); 26825 element(by.model('style2')).sendKeys('strike'); 26826 element(by.model('style3')).sendKeys('red'); 26827 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 26828 }); 26829 26830 it('array with map example should have 2 classes', function() { 26831 expect(ps.last().getAttribute('class')).toBe(''); 26832 element(by.model('style4')).sendKeys('bold'); 26833 element(by.model('warning')).click(); 26834 expect(ps.last().getAttribute('class')).toBe('bold orange'); 26835 }); 26836 </file> 26837 </example> 26838 26839 ## Animations 26840 26841 The example below demonstrates how to perform animations using ngClass. 26842 26843 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 26844 <file name="index.html"> 26845 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 26846 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 26847 <br> 26848 <span class="base-class" ng-class="myVar">Sample Text</span> 26849 </file> 26850 <file name="style.css"> 26851 .base-class { 26852 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 26853 } 26854 26855 .base-class.my-class { 26856 color: red; 26857 font-size:3em; 26858 } 26859 </file> 26860 <file name="protractor.js" type="protractor"> 26861 it('should check ng-class', function() { 26862 expect(element(by.css('.base-class')).getAttribute('class')).not. 26863 toMatch(/my-class/); 26864 26865 element(by.id('setbtn')).click(); 26866 26867 expect(element(by.css('.base-class')).getAttribute('class')). 26868 toMatch(/my-class/); 26869 26870 element(by.id('clearbtn')).click(); 26871 26872 expect(element(by.css('.base-class')).getAttribute('class')).not. 26873 toMatch(/my-class/); 26874 }); 26875 </file> 26876 </example> 26877 26878 26879 ## ngClass and pre-existing CSS3 Transitions/Animations 26880 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 26881 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 26882 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 26883 to view the step by step details of {@link $animate#addClass $animate.addClass} and 26884 {@link $animate#removeClass $animate.removeClass}. 26885 */ 26886var ngClassDirective = classDirective('', true); 26887 26888/** 26889 * @ngdoc directive 26890 * @name ngClassOdd 26891 * @restrict AC 26892 * 26893 * @description 26894 * The `ngClassOdd` and `ngClassEven` directives work exactly as 26895 * {@link ng.directive:ngClass ngClass}, except they work in 26896 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 26897 * 26898 * This directive can be applied only within the scope of an 26899 * {@link ng.directive:ngRepeat ngRepeat}. 26900 * 26901 * @element ANY 26902 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 26903 * of the evaluation can be a string representing space delimited class names or an array. 26904 * 26905 * @example 26906 <example> 26907 <file name="index.html"> 26908 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 26909 <li ng-repeat="name in names"> 26910 <span ng-class-odd="'odd'" ng-class-even="'even'"> 26911 {{name}} 26912 </span> 26913 </li> 26914 </ol> 26915 </file> 26916 <file name="style.css"> 26917 .odd { 26918 color: red; 26919 } 26920 .even { 26921 color: blue; 26922 } 26923 </file> 26924 <file name="protractor.js" type="protractor"> 26925 it('should check ng-class-odd and ng-class-even', function() { 26926 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 26927 toMatch(/odd/); 26928 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 26929 toMatch(/even/); 26930 }); 26931 </file> 26932 </example> 26933 */ 26934var ngClassOddDirective = classDirective('Odd', 0); 26935 26936/** 26937 * @ngdoc directive 26938 * @name ngClassEven 26939 * @restrict AC 26940 * 26941 * @description 26942 * The `ngClassOdd` and `ngClassEven` directives work exactly as 26943 * {@link ng.directive:ngClass ngClass}, except they work in 26944 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 26945 * 26946 * This directive can be applied only within the scope of an 26947 * {@link ng.directive:ngRepeat ngRepeat}. 26948 * 26949 * @element ANY 26950 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 26951 * result of the evaluation can be a string representing space delimited class names or an array.
26952 * 26953 * @example 26954 <example> 26955 <file name="index.html"> 26956 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 26957 <li ng-repeat="name in names"> 26958 <span ng-class-odd="'odd'" ng-class-even="'even'"> 26959 {{name}} 26960 </span> 26961 </li> 26962 </ol> 26963 </file> 26964 <file name="style.css"> 26965 .odd { 26966 color: red; 26967 } 26968 .even { 26969 color: blue; 26970 } 26971 </file> 26972 <file name="protractor.js" type="protractor"> 26973 it('should check ng-class-odd and ng-class-even', function() { 26974 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 26975 toMatch(/odd/); 26976 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 26977 toMatch(/even/); 26978 }); 26979 </file> 26980 </example> 26981 */ 26982var ngClassEvenDirective = classDirective('Even', 1); 26983 26984/** 26985 * @ngdoc directive 26986 * @name ngCloak 26987 * @restrict AC 26988 * 26989 * @description 26990 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 26991 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 26992 * directive to avoid the undesirable flicker effect caused by the html template display. 26993 * 26994 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 26995 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 26996 * of the browser view. 26997 * 26998 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 26999 * `angular.min.js`. 27000 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27001 * 27002 * ```css 27003 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 27004 * display: none !important; 27005 * } 27006 * ``` 27007 * 27008 * When this css rule is loaded by the browser, all html elements (including their children) that 27009 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 27010 * during the compilation of the template it deletes the `ngCloak` element attribute, making 27011 * the compiled element visible. 27012 * 27013 * For the best result, the `angular.js` script must be loaded in the head section of the html 27014 * document; alternatively, the css rule above must be included in the external stylesheet of the 27015 * application. 27016 * 27017 * @element ANY 27018 * 27019 * @example 27020 <example> 27021 <file name="index.html"> 27022 <div id="template1" ng-cloak>{{ 'hello' }}</div> 27023 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 27024 </file> 27025 <file name="protractor.js" type="protractor"> 27026 it('should remove the template directive and css class', function() { 27027 expect($('#template1').getAttribute('ng-cloak')). 27028 toBeNull(); 27029 expect($('#template2').getAttribute('ng-cloak')). 27030 toBeNull(); 27031 }); 27032 </file> 27033 </example> 27034 * 27035 */ 27036var ngCloakDirective = ngDirective({ 27037 compile: function(element, attr) { 27038 attr.$set('ngCloak', undefined); 27039 element.removeClass('ng-cloak'); 27040 } 27041}); 27042 27043/** 27044 * @ngdoc directive 27045 * @name ngController 27046 * 27047 * @description 27048 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 27049 * supports the principles behind the Model-View-Controller design pattern. 27050 * 27051 * MVC components in angular: 27052 * 27053 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 27054 * are accessed through bindings. 27055 * * View â The template (HTML with data bindings) that is rendered into the View. 27056 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 27057 * logic behind the application to decorate the scope with functions and values 27058 * 27059 * Note that you can also attach controllers to the DOM by declaring it in a route definition 27060 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 27061 * again using `ng-controller` in the template itself. This will cause the controller to be attached 27062 * and executed twice. 27063 * 27064 * @element ANY 27065 * @scope 27066 * @priority 500 27067 * @param {expression} ngController Name of a constructor function registered with the current 27068 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 27069 * that on the current scope evaluates to a constructor function. 27070 * 27071 * The controller instance can be published into a scope property by specifying 27072 * `ng-controller="as propertyName"`. 27073 * 27074 * If the current `$controllerProvider` is configured to use globals (via 27075 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 27076 * also be the name of a globally accessible constructor function (not recommended). 27077 * 27078 * @example 27079 * Here is a simple form for editing user contact information. Adding, remov
27079ing, clearing, and 27080 * greeting are methods declared on the controller (see source tab). These methods can 27081 * easily be called from the angular markup. Any changes to the data are automatically reflected 27082 * in the View without the need for a manual update. 27083 * 27084 * Two different declaration styles are included below: 27085 * 27086 * * one binds methods and properties directly onto the controller using `this`: 27087 * `ng-controller="SettingsController1 as settings"` 27088 * * one injects `$scope` into the controller: 27089 * `ng-controller="SettingsController2"` 27090 * 27091 * The second option is more common in the Angular community, and is generally used in boilerplates 27092 * and in this guide. However, there are advantages to binding properties directly to the controller 27093 * and avoiding scope. 27094 * 27095 * * Using `controller as` makes it obvious which controller you are accessing in the template when 27096 * multiple controllers apply to an element. 27097 * * If you are writing your controllers as classes you have easier access to the properties and 27098 * methods, which will appear on the scope, from inside the controller code. 27099 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 27100 * inheritance masking primitives. 27101 * 27102 * This example demonstrates the `controller as` syntax. 27103 * 27104 * <example name="ngControllerAs" module="controllerAsExample"> 27105 * <file name="index.html"> 27106 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 27107 * <label>Name: <input type="text" ng-model="settings.name"/></label> 27108 * <button ng-click="settings.greet()">greet</button><br/> 27109 * Contact: 27110 * <ul> 27111 * <li ng-repeat="contact in settings.contacts"> 27112 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 27113 * <option>phone</option> 27114 * <option>email</option> 27115 * </select> 27116 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 27117 * <button ng-click="settings.clearContact(contact)">clear</button> 27118 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 27119 * </li> 27120 * <li><button ng-click="settings.addContact()">add</button></li> 27121 * </ul> 27122 * </div> 27123 * </file> 27124 * <file name="app.js"> 27125 * angular.module('controllerAsExample', []) 27126 * .controller('SettingsController1', SettingsController1); 27127 * 27128 * function SettingsController1() { 27129 * this.name = "John Smith"; 27130 * this.contacts = [ 27131 * {type: 'phone', value: '408 555 1212'}, 27132 * {type: 'email', value: '[email protected]'} ]; 27133 * } 27134 * 27135 * SettingsController1.prototype.greet = function() { 27136 * alert(this.name); 27137 * }; 27138 * 27139 * SettingsController1.prototype.addContact = function() { 27140 * this.contacts.push({type: 'email', value: '[email protected]'}); 27141 * }; 27142 * 27143 * SettingsController1.prototype.removeContact = function(contactToRemove) { 27144 * var index = this.contacts.indexOf(contactToRemove); 27145 * this.contacts.splice(index, 1); 27146 * }; 27147 * 27148 * SettingsController1.prototype.clearContact = function(contact) { 27149 * contact.type = 'phone'; 27150 * contact.value = ''; 27151 * }; 27152 * </file> 27153 * <file name="protractor.js" type="protractor"> 27154 * it('should check controller as', function() { 27155 * var container = element(by.id('ctrl-as-exmpl')); 27156 * expect(container.element(by.model('settings.name')) 27157 * .getAttribute('value')).toBe('John Smith'); 27158 * 27159 * var firstRepeat = 27160 * container.element(by.repeater('contact in settings.contacts').row(0)); 27161 * var secondRepeat = 27162 * container.element(by.repeater('contact in settings.contacts').row(1)); 27163 * 27164 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 27165 * .toBe('408 555 1212'); 27166 * 27167 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 27168 * .toBe('[email protected]'); 27169 * 27170 * firstRepeat.element(by.buttonText('clear')).click(); 27171 * 27172 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 27173 * .toBe(''); 27174 * 27175 * container.element(by.buttonText('add')).click(); 27176 * 27177 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 27178 * .element(by.model('contact.value')) 27179 * .getAttribute('value')) 27180 * .toBe('[email protected]'); 27181 * }); 27182 * </file> 27183 * </example> 27184 * 27185 * This example demonstrates the "attach to `$scope`" style of controller. 27186 * 27187 * <example name="ngController" module="controllerExample"> 27188 * <file name="index.html"> 27189 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 27190 * <label>Name: <input type="text" ng-model="name"/></label> 27191 * <button ng-click="greet()">greet</button><br/> 27192 * Contact: 27193 * <ul> 27194 * <li ng-repeat="contact in contacts"> 27195 * <select ng-model="contact.type" id="select_{{$index}}"> 27196 * <option>phone</option> 27197 * <option>email</option> 27198 * </select> 27199 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 27200 * <button ng-click="clearContact(contact)">clear</button> 27201 * <button ng-click="removeContact(contact)">X</button> 27202 * </li> 27203 * <li>[ <button ng-click="addContact()">add</button> ]</li> 27204 * </ul> 27205 * </div> 27206 * </file> 27207 * <file name="app.js">
27208 * angular.module('controllerExample', []) 27209 * .controller('SettingsController2', ['$scope', SettingsController2]); 27210 * 27211 * function SettingsController2($scope) { 27212 * $scope.name = "John Smith"; 27213 * $scope.contacts = [ 27214 * {type:'phone', value:'408 555 1212'}, 27215 * {type:'email', value:'[email protected]'} ]; 27216 * 27217 * $scope.greet = function() { 27218 * alert($scope.name); 27219 * }; 27220 * 27221 * $scope.addContact = function() { 27222 * $scope.contacts.push({type:'email', value:'[email protected]'}); 27223 * }; 27224 * 27225 * $scope.removeContact = function(contactToRemove) { 27226 * var index = $scope.contacts.indexOf(contactToRemove); 27227 * $scope.contacts.splice(index, 1); 27228 * }; 27229 * 27230 * $scope.clearContact = function(contact) { 27231 * contact.type = 'phone'; 27232 * contact.value = ''; 27233 * }; 27234 * } 27235 * </file> 27236 * <file name="protractor.js" type="protractor"> 27237 * it('should check controller', function() { 27238 * var container = element(by.id('ctrl-exmpl')); 27239 * 27240 * expect(container.element(by.model('name')) 27241 * .getAttribute('value')).toBe('John Smith'); 27242 * 27243 * var firstRepeat = 27244 * container.element(by.repeater('contact in contacts').row(0)); 27245 * var secondRepeat = 27246 * container.element(by.repeater('contact in contacts').row(1)); 27247 * 27248 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 27249 * .toBe('408 555 1212'); 27250 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 27251 * .toBe('[email protected]'); 27252 * 27253 * firstRepeat.element(by.buttonText('clear')).click(); 27254 * 27255 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 27256 * .toBe(''); 27257 * 27258 * container.element(by.buttonText('add')).click(); 27259 * 27260 * expect(container.element(by.repeater('contact in contacts').row(2)) 27261 * .element(by.model('contact.value')) 27262 * .getAttribute('value')) 27263 * .toBe('[email protected]'); 27264 * }); 27265 * </file> 27266 *</example> 27267 27268 */ 27269var ngControllerDirective = [function() { 27270 return { 27271 restrict: 'A', 27272 scope: true, 27273 controller: '@', 27274 priority: 500 27275 }; 27276}]; 27277 27278/** 27279 * @ngdoc directive 27280 * @name ngCsp 27281 * 27282 * @element html 27283 * @description 27284 * 27285 * Angular has some features that can break certain 27286 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 27287 * 27288 * If you intend to implement these rules then you must tell Angular not to use these features. 27289 * 27290 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 27291 * 27292 * 27293 * The following rules affect Angular: 27294 * 27295 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions 27296 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30% 27297 * increase in the speed of evaluating Angular expressions. 27298 * 27299 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular 27300 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). 27301 * To make these directives work when a CSP rule is blocking inline styles, you must link to the 27302 * `angular-csp.css` in your HTML manually. 27303 * 27304 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval 27305 * and automatically deactivates this feature in the {@link $parse} service. This autodetection, 27306 * however, triggers a CSP error to be logged in the console: 27307 * 27308 * ``` 27309 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 27310 * script in the following Content Security Policy directive: "default-src 'self'". Note that 27311 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 27312 * ``` 27313 * 27314 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 27315 * directive on an element of the HTML document that appears before the `<script>` tag that loads 27316 * the `angular.js` file. 27317 * 27318 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 27319 * 27320 * You can specify which of the CSP related Angular features should be deactivated by providing 27321 * a value for the `ng-csp` attribute. The options are as follows: 27322 * 27323 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM 27324 *
27325 * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings 27326 * 27327 * You can use these values in the following combinations: 27328 * 27329 * 27330 * * No declaration means that Angular will assume that you can do inline styles, but it will do 27331 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions 27332 * of Angular. 27333 * 27334 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline 27335 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions 27336 * of Angular. 27337 * 27338 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject 27339 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 27340 * 27341 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can 27342 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 27343 * 27344 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject 27345 * styles nor use eval, which is the same as an empty: ng-csp. 27346 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 27347 * 27348 * @example 27349 * This example shows how to apply the `ngCsp` directive to the `html` tag. 27350 ```html 27351 <!doctype html> 27352 <html ng-app ng-csp> 27353 ... 27354 ... 27355 </html> 27356 ``` 27357 * @example 27358 // Note: the suffix `.csp` in the example name triggers 27359 // csp mode in our http server! 27360 <example name="example.csp" module="cspExample" ng-csp="true"> 27361 <file name="index.html"> 27362 <div ng-controller="MainController as ctrl"> 27363 <div> 27364 <button ng-click="ctrl.inc()" id="inc">Increment</button> 27365 <span id="counter"> 27366 {{ctrl.counter}} 27367 </span> 27368 </div> 27369 27370 <div> 27371 <button ng-click="ctrl.evil()" id="evil">Evil</button> 27372 <span id="evilError"> 27373 {{ctrl.evilError}} 27374 </span> 27375 </div> 27376 </div> 27377 </file> 27378 <file name="script.js"> 27379 angular.module('cspExample', []) 27380 .controller('MainController', function() { 27381 this.counter = 0; 27382 this.inc = function() { 27383 this.counter++; 27384 }; 27385 this.evil = function() { 27386 // jshint evil:true 27387 try { 27388 eval('1+2'); 27389 } catch (e) { 27390 this.evilError = e.message; 27391 } 27392 }; 27393 }); 27394 </file> 27395 <file name="protractor.js" type="protractor"> 27396 var util, webdriver; 27397 27398 var incBtn = element(by.id('inc')); 27399 var counter = element(by.id('counter')); 27400 var evilBtn = element(by.id('evil')); 27401 var evilError = element(by.id('evilError')); 27402 27403 function getAndClearSevereErrors() { 27404 return browser.manage().logs().get('browser').then(function(browserLog) { 27405 return browserLog.filter(function(logEntry) { 27406 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 27407 }); 27408 }); 27409 } 27410 27411 function clearErrors() { 27412 getAndClearSevereErrors(); 27413 } 27414 27415 function expectNoErrors() { 27416 getAndClearSevereErrors().then(function(filteredLog) { 27417 expect(filteredLog.length).toEqual(0); 27418 if (filteredLog.length) { 27419 console.log('browser console errors: ' + util.inspect(filteredLog)); 27420 } 27421 }); 27422 } 27423 27424 function expectError(regex) { 27425 getAndClearSevereErrors().then(function(filteredLog) { 27426 var found = false; 27427 filteredLog.forEach(function(log) { 27428 if (log.message.match(regex)) { 27429 found = true; 27430 } 27431 }); 27432 if (!found) { 27433 throw new Error('expected an error that matches ' + regex); 27434 } 27435 }); 27436 } 27437 27438 beforeEach(function() { 27439 util = require('util'); 27440 webdriver = require('protractor/node_modules/selenium-webdriver'); 27441 }); 27442 27443 // For now, we only test on Chrome, 27444 // as Safari does not load the page with Protractor's injected scripts, 27445 // and Firefox webdriver always disables content security policy (#6358) 27446 if (browser.params.browser !== 'chrome') { 27447 return; 27448 } 27449 27450 it('should not report errors when the page is loaded', function() { 27451 // clear errors so we are not dependent on previous tests 27452 clearErrors(); 27453 // Need to reload the page as the page is already loaded when 27454 // we come here 27455 browser.driver.getCurrentUrl().then(function(url) { 27456 browser.get(url); 27457 }); 27458 expectNoErrors(); 27459 }); 27460 27461 it('should evaluate expressions', function() { 27462 expect(counter.getText()).toEqual('0'); 27463 incBtn.click(); 27464 expect(counter.getText()).toEqual('1'); 27465 expectNoErrors(); 27466 }); 27467 27468 it('should throw and report an error when using "eval"', function() { 27469 evilBtn.click(); 27470 expect(evilError.getText()).toMatch(/Content Security Policy/); 27471 expectError(/Content Security Policy/); 27472 }); 27473 </file> 27474 </example> 27475 */ 27476 27477// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
27478// bootstrap the system (before $parse is instantiated), for this reason we just have 27479// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc 27480 27481/** 27482 * @ngdoc directive 27483 * @name ngClick 27484 * 27485 * @description 27486 * The ngClick directive allows you to specify custom behavior when 27487 * an element is clicked. 27488 * 27489 * @element ANY 27490 * @priority 0 27491 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 27492 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 27493 * 27494 * @example 27495 <example> 27496 <file name="index.html"> 27497 <button ng-click="count = count + 1" ng-init="count=0"> 27498 Increment 27499 </button> 27500 <span> 27501 count: {{count}} 27502 </span> 27503 </file> 27504 <file name="protractor.js" type="protractor"> 27505 it('should check ng-click', function() { 27506 expect(element(by.binding('count')).getText()).toMatch('0'); 27507 element(by.css('button')).click(); 27508 expect(element(by.binding('count')).getText()).toMatch('1'); 27509 }); 27510 </file> 27511 </example> 27512 */ 27513/* 27514 * A collection of directives that allows creation of custom event handlers that are defined as 27515 * angular expressions and are compiled and executed within the current scope. 27516 */ 27517var ngEventDirectives = {}; 27518 27519// For events that might fire synchronously during DOM manipulation 27520// we need to execute their event handlers asynchronously using $evalAsync, 27521// so that they are not executed in an inconsistent state. 27522var forceAsyncEvents = { 27523 'blur': true, 27524 'focus': true 27525}; 27526forEach( 27527 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 27528 function(eventName) { 27529 var directiveName = directiveNormalize('ng-' + eventName); 27530 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 27531 return { 27532 restrict: 'A', 27533 compile: function($element, attr) { 27534 // We expose the powerful $event object on the scope that provides access to the Window, 27535 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 27536 // checks at the cost of speed since event handler expressions are not executed as 27537 // frequently as regular change detection. 27538 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 27539 return function ngEventHandler(scope, element) { 27540 element.on(eventName, function(event) { 27541 var callback = function() { 27542 fn(scope, {$event:event}); 27543 }; 27544 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 27545 scope.$evalAsync(callback); 27546 } else { 27547 scope.$apply(callback); 27548 } 27549 }); 27550 }; 27551 } 27552 }; 27553 }]; 27554 } 27555); 27556 27557/** 27558 * @ngdoc directive 27559 * @name ngDblclick 27560 * 27561 * @description 27562 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 27563 * 27564 * @element ANY 27565 * @priority 0 27566 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 27567 * a dblclick. (The Event object is available as `$event`) 27568 * 27569 * @example 27570 <example> 27571 <file name="index.html"> 27572 <button ng-dblclick="count = count + 1" ng-init="count=0"> 27573 Increment (on double click) 27574 </button> 27575 count: {{count}} 27576 </file> 27577 </example> 27578 */ 27579 27580 27581/** 27582 * @ngdoc directive 27583 * @name ngMousedown 27584 * 27585 * @description 27586 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 27587 * 27588 * @element ANY 27589 * @priority 0 27590 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 27591 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 27592 * 27593 * @example 27594 <example> 27595 <file name="index.html"> 27596 <button ng-mousedown="count = count + 1" ng-init="count=0"> 27597 Increment (on mouse down) 27598 </button> 27599 count: {{count}} 27600 </file> 27601 </example> 27602 */ 27603 27604 27605/** 27606 * @ngdoc directive 27607 * @name ngMouseup 27608 * 27609 * @description 27610 * Specify custom behavior on mouseup event. 27611 * 27612 * @element ANY 27613 * @priority 0 27614 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 27615 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 27616 * 27617 * @example 27618 <example> 27619 <file name="index.html"> 27620 <button ng-mouseup="count = count + 1" ng-init="count=0"> 27621 Increment (on mouse up) 27622 </button> 27623 count: {{count}} 27624 </file> 27625 </example> 27626 */ 27627 27628/** 27629 * @ngdoc directive 27630 * @name ngMouseover 27631 * 27632 * @description 27633 * Specify custom behavior on mouseover event. 27634 * 27635 * @element ANY 27636 * @priority 0 27637 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 27638 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 27639 * 27640 * @example 27641 <example> 27642 <file name="index.html"> 27643 <button ng-mouseover="count = count + 1" ng-init="count=0"> 27644 Increment (when mouse is over) 27645 </button> 27646 count: {{count}} 27647 </file> 27648 </example> 27649 */ 27650 27651 27652/** 27653 * @ngdoc directive 27654 * @name ngMouseenter 27655 * 27656 * @description 27657 * Specify custom behavior on mouseenter event. 27658 * 27659 * @element ANY 27660 * @priority 0 27661 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 27662 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 27663 * 27664 * @example 27665 <example> 27666 <file name="index.html"> 27667 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 27668 Increment (when mouse enters) 27669 </button> 27670 count: {{count}} 27671 </file> 27672 </example> 27673 */ 27674 27675 27676/** 27677 * @ngdoc directive 27678 * @name ngMouseleave 27679 * 27680 * @description 27681 * Specify custom behavior on mouseleave event. 27682 * 27683 * @element ANY 27684 * @priority 0 27685 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 27686 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 27687 * 27688 * @example 27689 <example> 27690 <file name="index.html"> 27691 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 27692 Increment (when mouse leaves) 27693 </button> 27694 count: {{count}} 27695 </file> 27696 </example> 27697 */ 27698 27699 27700/** 27701 * @ngdoc directive 27702 * @name ngMousemove 27703 * 27704 * @description 27705 * Specify custom behavior on mousemove event. 27706 * 27707 * @element ANY 27708 * @priority 0 27709 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 27710 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 27711 * 27712 * @example 27713 <example> 27714 <file name="index.html"> 27715 <button ng-mousemove="count = count + 1" ng-init="count=0"> 27716 Increment (when mouse moves) 27717 </button> 27718 count: {{count}} 27719 </file> 27720 </example> 27721 */ 27722 27723 27724/** 27725 * @ngdoc directive 27726 * @name ngKeydown 27727 * 27728 * @description 27729 * Specify custom behavior on keydown event. 27730 * 27731 * @element ANY 27732 * @priority 0 27733 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 27734 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 27735 * 27736 * @example 27737 <example> 27738 <file name="index.html"> 27739 <input ng-keydown="count = count + 1" ng-init="count=0"> 27740 key down count: {{count}} 27741 </file> 27742 </example> 27743 */ 27744 27745 27746/** 27747 * @ngdoc directive 27748 * @name ngKeyup 27749 * 27750 * @description 27751 * Specify custom behavior on keyup event. 27752 * 27753 * @element ANY 27754 * @priority 0 27755 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 27756 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 27757 * 27758 * @example 27759 <example> 27760 <file name="index.html"> 27761 <p>Typing in the input box below updates the key count</p> 27762 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 27763 27764 <p>Typing in the input box below updates the keycode</p> 27765 <input ng-keyup="event=$event"> 27766 <p>event keyCode: {{ event.keyCode }}</p> 27767 <p>event altKey: {{ event.altKey }}</p> 27768 </file> 27769 </example> 27770 */ 27771 27772 27773/** 27774 * @ngdoc directive 27775 * @name ngKeypress 27776 * 27777 * @description 27778 * Specify custom behavior on keypress event. 27779 * 27780 * @element ANY 27781 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 27782 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 27783 * and can be interrogated for keyCode, altKey, etc.) 27784 * 27785 * @example 27786 <example> 27787 <file name="index.html"> 27788 <input ng-keypress="count = count + 1" ng-init="count=0"> 27789 key press count: {{count}} 27790 </file> 27791 </example> 27792 */ 27793 27794 27795/** 27796 * @ngdoc directive 27797 * @name ngSubmit 27798 * 27799 * @description 27800 * Enables binding angular expressions to onsubmit events. 27801 * 27802 * Additionally it prevents the default action (which for form means
27802sending the request to the 27803 * server and reloading the current page), but only if the form does not contain `action`, 27804 * `data-action`, or `x-action` attributes. 27805 * 27806 * <div class="alert alert-warning"> 27807 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 27808 * `ngSubmit` handlers together. See the 27809 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 27810 * for a detailed discussion of when `ngSubmit` may be triggered. 27811 * </div> 27812 * 27813 * @element form 27814 * @priority 0 27815 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 27816 * ({@link guide/expression#-event- Event object is available as `$event`}) 27817 * 27818 * @example 27819 <example module="submitExample"> 27820 <file name="index.html"> 27821 <script> 27822 angular.module('submitExample', []) 27823 .controller('ExampleController', ['$scope', function($scope) { 27824 $scope.list = []; 27825 $scope.text = 'hello'; 27826 $scope.submit = function() { 27827 if ($scope.text) { 27828 $scope.list.push(this.text); 27829 $scope.text = ''; 27830 } 27831 }; 27832 }]); 27833 </script>
27834 <form ng-submit="submit()" ng-controller="ExampleController"> 27835 Enter text and hit enter: 27836 <input type="text" ng-model="text" name="text" /> 27837 <input type="submit" id="submit" value="Submit" /> 27838 <pre>list={{list}}</pre> 27839 </form> 27840 </file> 27841 <file name="protractor.js" type="protractor"> 27842 it('should check ng-submit', function() { 27843 expect(element(by.binding('list')).getText()).toBe('list=[]'); 27844 element(by.css('#submit')).click(); 27845 expect(element(by.binding('list')).getText()).toContain('hello'); 27846 expect(element(by.model('text')).getAttribute('value')).toBe(''); 27847 }); 27848 it('should ignore empty strings', function() { 27849 expect(element(by.binding('list')).getText()).toBe('list=[]'); 27850 element(by.css('#submit')).click(); 27851 element(by.css('#submit')).click(); 27852 expect(element(by.binding('list')).getText()).toContain('hello'); 27853 }); 27854 </file> 27855 </example> 27856 */ 27857 27858/** 27859 * @ngdoc directive 27860 * @name ngFocus 27861 * 27862 * @description 27863 * Specify custom behavior on focus event. 27864 * 27865 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 27866 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 27867 * during an `$apply` to ensure a consistent state. 27868 * 27869 * @element window, input, select, textarea, a 27870 * @priority 0 27871 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 27872 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 27873 * 27874 * @example 27875 * See {@link ng.directive:ngClick ngClick} 27876 */ 27877 27878/** 27879 * @ngdoc directive 27880 * @name ngBlur 27881 * 27882 * @description 27883 * Specify custom behavior on blur event. 27884 * 27885 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 27886 * an element has lost focus. 27887 * 27888 * Note: As the `blur` event is executed synchronously also during DOM manipulations 27889 * (e.g. removing a focussed input), 27890 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 27891 * during an `$apply` to ensure a consistent state. 27892 * 27893 * @element window, input, select, textarea, a 27894 * @priority 0 27895 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 27896 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 27897 * 27898 * @example 27899 * See {@link ng.directive:ngClick ngClick} 27900 */ 27901 27902/** 27903 * @ngdoc directive 27904 * @name ngCopy 27905 * 27906 * @description 27907 * Specify custom behavior on copy event. 27908 * 27909 * @element window, input, select, textarea, a 27910 * @priority 0 27911 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 27912 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 27913 * 27914 * @example 27915 <example> 27916 <file name="index.html"> 27917 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 27918 copied: {{copied}} 27919 </file> 27920 </example> 27921 */ 27922 27923/** 27924 * @ngdoc directive 27925 * @name ngCut 27926 * 27927 * @description 27928 * Specify custom behavior on cut event. 27929 * 27930 * @element window, input, select, textarea, a 27931 * @priority 0 27932 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 27933 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 27934 * 27935 * @example 27936 <example> 27937 <file name="index.html"> 27938 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 27939 cut: {{cut}} 27940 </file> 27941 </example> 27942 */ 27943 27944/** 27945 * @ngdoc directive 27946 * @name ngPaste 27947 * 27948 * @description 27949 * Specify custom behavior on paste event. 27950 * 27951 * @element window, input, select, textarea, a 27952 * @priority 0 27953 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 27954 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 27955 * 27956 * @example 27957 <example> 27958 <file name="index.html"> 27959 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 27960 pasted: {{paste}} 27961 </file> 27962 </example> 27963 */ 27964 27965/** 27966 * @ngdoc directive 27967 * @name ngIf 27968 * @restrict A 27969 * @multiElement 27970 * 27971 * @description 27972 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 27973 * {expression}. If the expression assigned to `ngIf` evaluates to a false 27974 * value then the element is removed from the DOM, otherwise a clone of the 27975 * element is reinserted into the DOM. 27976 * 27977 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 27978 * element in the DOM rather than changing its visibility via the `display` css property. A common 27979 * case when this difference is significant is when using css selectors that rely on an element's 27980 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
27980s. 27981 * 27982 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 27983 * is created when the element is restored. The scope created within `ngIf` inherits from 27984 * its parent scope using 27985 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 27986 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 27987 * a javascript primitive defined in the parent scope. In this case any modifications made to the 27988 * variable within the child scope will override (hide) the value in the parent scope. 27989 * 27990 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 27991 * is if an element's class attribute is directly modified after it's compiled, using something like 27992 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 27993 * the added class will be lost because the original compiled state is used to regenerate the element. 27994 * 27995 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 27996 * and `leave` effects. 27997 * 27998 * @animations 27999 * | Animation | Occurs | 28000 * |----------------------------------|-------------------------------------| 28001 * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container | 28002 * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM | 28003 * 28004 * @element ANY 28005 * @scope 28006 * @priority 600 28007 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 28008 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 28009 * element is added to the DOM tree. 28010 * 28011 * @example 28012 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 28013 <file name="index.html"> 28014 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 28015 Show when checked: 28016 <span ng-if="checked" class="animate-if"> 28017 This is removed when the checkbox is unchecked. 28018 </span> 28019 </file> 28020 <file name="animations.css"> 28021 .animate-if { 28022 background:white; 28023 border:1px solid black; 28024 padding:10px; 28025 } 28026 28027 .animate-if.ng-enter, .animate-if.ng-leave { 28028 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 28029 } 28030 28031 .animate-if.ng-enter, 28032 .animate-if.ng-leave.ng-leave-active { 28033 opacity:0; 28034 } 28035 28036 .animate-if.ng-leave, 28037 .animate-if.ng-enter.ng-enter-active { 28038 opacity:1; 28039 } 28040 </file> 28041 </example> 28042 */ 28043var ngIfDirective = ['$animate', '$compile', function($animate, $compile) { 28044 return { 28045 multiElement: true, 28046 transclude: 'element', 28047 priority: 600, 28048 terminal: true, 28049 restrict: 'A', 28050 $$tlb: true, 28051 link: function($scope, $element, $attr, ctrl, $transclude) { 28052 var block, childScope, previousElements; 28053 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 28054 28055 if (value) { 28056 if (!childScope) { 28057 $transclude(function(clone, newScope) { 28058 childScope = newScope; 28059 clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf); 28060 // Note: We only need the first/last node of the cloned nodes. 28061 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 28062 // by a directive with templateUrl when its template arrives. 28063 block = { 28064 clone: clone 28065 }; 28066 $animate.enter(clone, $element.parent(), $element); 28067 }); 28068 } 28069 } else { 28070 if (previousElements) { 28071 previousElements.remove(); 28072 previousElements = null; 28073 } 28074 if (childScope) { 28075 childScope.$destroy(); 28076 childScope = null; 28077 } 28078 if (block) { 28079 previousElements = getBlockNodes(block.clone); 28080 $animate.leave(previousElements).then(function() { 28081 previousElements = null; 28082 }); 28083 block = null; 28084 } 28085 } 28086 }); 28087 } 28088 }; 28089}]; 28090 28091/** 28092 * @ngdoc directive 28093 * @name ngInclude 28094 * @restrict ECA 28095 * 28096 * @description 28097 * Fetches, compiles and includes an external HTML fragment. 28098 * 28099 * By default, the template URL is restricted to the same domain and protocol as the 28100 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 28101 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 28102 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 28103 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 28104 * ng.$sce Strict Contextual Escaping}. 28105 * 28106 * In addition, the browser's 28107 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 28108 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 28109 * policy may further restrict whether the template is successfully loaded. 28110 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 28111 * access on some browsers. 28112 * 28113 * @animations 28114 * | Animation | Occurs | 28115 * |----------------------------------|-------------------------------------| 28116 * | {@link ng.$animate#enter enter} | when the expression changes, on the new include | 28117 * | {@link ng.$animate#leave leave} | when the expression changes, on the old include | 28118 * 28119 * The enter and leave animation occur concurrently. 28120 * 28121 * @scope 28122 * @priority 400 28123 * 28124 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 28125 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 28126 * @param {string=} onload Expression to evaluate when a new partial is loaded. 28127 * <div class="alert alert-warning"> 28128 * **Note:** When using onload on SVG elements in IE11, the browser will try to call 28129 * a function with the name on the window element, which will usually throw a 28130 * "function is undefined" error. To fix this, you can instead use `data-onload` or a 28131 * different form that {@link guide/directive#normalization matches} `onload`. 28132 * </div> 28133 * 28134 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 28135 * $anchorScroll} to scroll the viewport after the content is loaded. 28136 * 28137 * - If the attribute is not set, disable scrolling. 28138 * - If the attribute is set without value, enable scrolling. 28139 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 28140 * 28141 * @example 28142 <example module="includeExample" deps="angular-animate.js" animations="true"> 28143 <file name="index.html">
28144 <div ng-controller="ExampleController"> 28145 <select ng-model="template" ng-options="t.name for t in templates"> 28146 <option value="">(blank)</option> 28147 </select> 28148 url of the template: <code>{{template.url}}</code> 28149 <hr/> 28150 <div class="slide-animate-container"> 28151 <div class="slide-animate" ng-include="template.url"></div> 28152 </div> 28153 </div> 28154 </file> 28155 <file name="script.js"> 28156 angular.module('includeExample', ['ngAnimate']) 28157 .controller('ExampleController', ['$scope', function($scope) { 28158 $scope.templates = 28159 [ { name: 'template1.html', url: 'template1.html'}, 28160 { name: 'template2.html', url: 'template2.html'} ]; 28161 $scope.template = $scope.templates[0]; 28162 }]); 28163 </file> 28164 <file name="template1.html"> 28165 Content of template1.html 28166 </file> 28167 <file name="template2.html"> 28168 Content of template2.html 28169 </file> 28170 <file name="animations.css"> 28171 .slide-animate-container { 28172 position:relative; 28173 background:white; 28174 border:1px solid black; 28175 height:40px; 28176 overflow:hidden; 28177 } 28178 28179 .slide-animate { 28180 padding:10px; 28181 } 28182 28183 .slide-animate.ng-enter, .slide-animate.ng-leave { 28184 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 28185 28186 position:absolute; 28187 top:0; 28188 left:0; 28189 right:0; 28190 bottom:0; 28191 display:block; 28192 padding:10px; 28193 } 28194 28195 .slide-animate.ng-enter { 28196 top:-50px; 28197 } 28198 .slide-animate.ng-enter.ng-enter-active { 28199 top:0; 28200 } 28201 28202 .slide-animate.ng-leave { 28203 top:0; 28204 } 28205 .slide-animate.ng-leave.ng-leave-active { 28206 top:50px; 28207 } 28208 </file> 28209 <file name="protractor.js" type="protractor"> 28210 var templateSelect = element(by.model('template')); 28211 var includeElem = element(by.css('[ng-include]')); 28212 28213 it('should load template1.html', function() { 28214 expect(includeElem.getText()).toMatch(/Content of template1.html/); 28215 }); 28216 28217 it('should load template2.html', function() { 28218 if (browser.params.browser == 'firefox') { 28219 // Firefox can't handle using selects 28220 // See https://github.com/angular/protractor/issues/480 28221 return; 28222 } 28223 templateSelect.click(); 28224 templateSelect.all(by.css('option')).get(2).click(); 28225 expect(includeElem.getText()).toMatch(/Content of template2.html/); 28226 }); 28227 28228 it('should change to blank', function() { 28229 if (browser.params.browser == 'firefox') { 28230 // Firefox can't handle using selects 28231 return; 28232 } 28233 templateSelect.click(); 28234 templateSelect.all(by.css('option')).get(0).click(); 28235 expect(includeElem.isPresent()).toBe(false); 28236 }); 28237 </file> 28238 </example> 28239 */ 28240 28241 28242/** 28243 * @ngdoc event 28244 * @name ngInclude#$includeContentRequested 28245 * @eventType emit on the scope ngInclude was declared in 28246 * @description 28247 * Emitted every time the ngInclude content is requested. 28248 * 28249 * @param {Object} angularEvent Synthetic event object. 28250 * @param {String} src URL of content to load. 28251 */ 28252 28253 28254/** 28255 * @ngdoc event 28256 * @name ngInclude#$includeContentLoaded 28257 * @eventType emit on the current ngInclude scope 28258 * @description 28259 * Emitted every time the ngInclude content is reloaded. 28260 * 28261 * @param {Object} angularEvent Synthetic event object. 28262 * @param {String} src URL of content to load. 28263 */ 28264 28265 28266/** 28267 * @ngdoc event 28268 * @name ngInclude#$includeContentError 28269 * @eventType emit on the scope ngInclude was declared in 28270 * @description 28271 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 28272 * 28273 * @param {Object} angularEvent Synthetic event object. 28274 * @param {String} src URL of content to load. 28275 */ 28276var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 28277 function($templateRequest, $anchorScroll, $animate) { 28278 return { 28279 restrict: 'ECA', 28280 priority: 400, 28281 terminal: true, 28282 transclude: 'element', 28283 controller: angular.noop, 28284 compile: function(element, attr) { 28285 var srcExp = attr.ngInclude || attr.src, 28286 onloadExp = attr.onload || '', 28287 autoScrollExp = attr.autoscroll; 28288 28289 return function(scope, $element, $attr, ctrl, $transclude) { 28290 var changeCounter = 0, 28291 currentScope, 28292 previousElement, 28293 currentElement; 28294 28295 var cleanupLastIncludeContent = function() { 28296 if (previousElement) { 28297 previousElement.remove(); 28298 previousElement = null; 28299 } 28300 if (currentScope) { 28301 currentScope.$destroy(); 28302 currentScope = null; 28303 } 28304 if (currentElement) { 28305 $animate.leave(currentElement).then(function() { 28306 previousElement = null; 28307 }); 28308 previousElement = currentElement; 28309 currentElement = null; 28310 } 28311 }; 28312 28313 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 28314 var afterAnimation = function() { 28315 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 28316 $anchorScroll(); 28317 } 28318 }; 28319 var thisChangeId = ++changeCounter; 28320 28321 if (src) { 28322 //set the 2nd param to true to ignore the template request error so that the inner 28323 //contents and scope can be cleaned up. 28324 $templateRequest(src, true).then(function(response) { 28325 if (scope.$$destroyed) return; 28326 28327 if (thisChangeId !== changeCounter) return; 28328 var newScope = scope.$new(); 28329 ctrl.template = response; 28330 28331 // Note: This will also link all children of ng-include that were contained in the original 28332 // html. If that content contains controllers, ... they could pollute/change the scope. 28333 // However, using ng-include on an element with additional content does not make sense... 28334 // Note: We can't remove them in the cloneAttchFn of $transclude as that 28335 // function is called before linking the content, which would apply child 28336 // directives to non existing elements. 28337 var clone = $transclude(newScope, function(clone) { 28338 cleanupLastIncludeContent(); 28339 $animate.enter(clone, null, $element).then(afterAnimation); 28340 }); 28341 28342 currentScope = newScope; 28343 currentElement = clone; 28344 28345 currentScope.$emit('$includeContentLoaded', src); 28346 scope.$eval(onloadExp); 28347 }, function() { 28348 if (scope.$$destroyed) return; 28349 28350 if (thisChangeId === changeCounter) { 28351 cleanupLastIncludeContent();
vendor: 2,842 bytes, lines 28352-28434
28352 scope.$emit('$includeContentError', src); 28353 } 28354 }); 28355 scope.$emit('$includeContentRequested', src); 28356 } else { 28357 cleanupLastIncludeContent(); 28358 ctrl.template = null; 28359 } 28360 }); 28361 }; 28362 } 28363 }; 28364}]; 28365 28366// This directive is called during the $transclude call of the first `ngInclude` directive. 28367// It will replace and compile the content of the element with the loaded template. 28368// We need this directive so that the element content is already filled when 28369// the link function of another directive on the same element as ngInclude 28370// is called. 28371var ngIncludeFillContentDirective = ['$compile', 28372 function($compile) { 28373 return { 28374 restrict: 'ECA', 28375 priority: -400, 28376 require: 'ngInclude', 28377 link: function(scope, $element, $attr, ctrl) { 28378 if (toString.call($element[0]).match(/SVG/)) { 28379 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 28380 // support innerHTML, so detect this here and try to generate the contents 28381 // specially. 28382 $element.empty(); 28383 $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope, 28384 function namespaceAdaptedClone(clone) { 28385 $element.append(clone); 28386 }, {futureParentElement: $element}); 28387 return; 28388 } 28389 28390 $element.html(ctrl.template); 28391 $compile($element.contents())(scope); 28392 } 28393 }; 28394 }]; 28395 28396/** 28397 * @ngdoc directive 28398 * @name ngInit 28399 * @restrict AC 28400 * 28401 * @description 28402 * The `ngInit` directive allows you to evaluate an expression in the 28403 * current scope. 28404 * 28405 * <div class="alert alert-danger"> 28406 * This directive can be abused to add unnecessary amounts of logic into your templates. 28407 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of 28408 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via 28409 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} 28410 * rather than `ngInit` to initialize values on a scope. 28411 * </div> 28412 * 28413 * <div class="alert alert-warning"> 28414 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 28415 * sure you have parentheses to ensure correct operator precedence: 28416 * <pre class="prettyprint"> 28417 * `<div ng-init="test1 = ($index | toString)"></div>` 28418 * </pre> 28419 * </div> 28420 * 28421 * @priority 450 28422 * 28423 * @element ANY 28424 * @param {expression} ngInit {@link guide/expression Expression} to eval. 28425 * 28426 * @example 28427 <example module="initExample"> 28428 <file name="index.html"> 28429 <script> 28430 angular.module('initExample', []) 28431 .controller('ExampleController', ['$scope', function($scope) { 28432 $scope.list = [['a', 'b'], ['c', 'd']]; 28433 }]); 28434 </script>
28435 <div ng-controller="ExampleController"> 28436 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 28437 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 28438 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 28439 </div> 28440 </div> 28441 </div> 28442 </file> 28443 <file name="protractor.js" type="protractor"> 28444 it('should alias index positions', function() { 28445 var elements = element.all(by.css('.example-init')); 28446 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 28447 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 28448 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 28449 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 28450 }); 28451 </file> 28452 </example> 28453 */ 28454var ngInitDirective = ngDirective({ 28455 priority: 450, 28456 compile: function() { 28457 return { 28458 pre: function(scope, element, attrs) { 28459 scope.$eval(attrs.ngInit); 28460 } 28461 }; 28462 } 28463}); 28464 28465/** 28466 * @ngdoc directive 28467 * @name ngList 28468 * 28469 * @description 28470 * Text input that converts between a delimited string and an array of strings. The default 28471 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 28472 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 28473 * 28474 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 28475 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 28476 * list item is respected. This implies that the user of the directive is responsible for 28477 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 28478 * tab or newline character. 28479 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 28480 * when joining the list items back together) and whitespace around each list item is stripped 28481 * before it is added to the model. 28482 * 28483 * ### Example with Validation 28484 * 28485 * <example name="ngList-directive" module="listExample"> 28486 * <file name="app.js"> 28487 * angular.module('listExample', []) 28488 * .controller('ExampleController', ['$scope', function($scope) { 28489 * $scope.names = ['morpheus', 'neo', 'trinity']; 28490 * }]); 28491 * </file> 28492 * <file name="index.html">
28493 * <form name="myForm" ng-controller="ExampleController"> 28494 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 28495 * <span role="alert"> 28496 * <span class="error" ng-show="myForm.namesInput.$error.required"> 28497 * Required!</span> 28498 * </span> 28499 * <br> 28500 * <tt>names = {{names}}</tt><br/> 28501 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 28502 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 28503 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 28504 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 28505 * </form> 28506 * </file> 28507 * <file name="protractor.js" type="protractor"> 28508 * var listInput = element(by.model('names')); 28509 * var names = element(by.exactBinding('names')); 28510 * var valid = element(by.binding('myForm.namesInput.$valid')); 28511 * var error = element(by.css('span.error')); 28512 * 28513 * it('should initialize to model', function() { 28514 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 28515 * expect(valid.getText()).toContain('true'); 28516 * expect(error.getCssValue('display')).toBe('none'); 28517 * }); 28518 * 28519 * it('should be invalid if empty', function() { 28520 * listInput.clear(); 28521 * listInput.sendKeys(''); 28522 * 28523 * expect(names.getText()).toContain(''); 28524 * expect(valid.getText()).toContain('false'); 28525 * expect(error.getCssValue('display')).not.toBe('none'); 28526 * }); 28527 * </file> 28528 * </example> 28529 * 28530 * ### Example - splitting on newline 28531 * <example name="ngList-directive-newlines"> 28532 * <file name="index.html"> 28533 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 28534 * <pre>{{ list | json }}</pre> 28535 * </file> 28536 * <file name="protractor.js" type="protractor"> 28537 * it("should split the text by newlines", function() { 28538 * var listInput = element(by.model('list')); 28539 * var output = element(by.binding('list | json')); 28540 * listInput.sendKeys('abc\ndef\nghi'); 28541 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 28542 * }); 28543 * </file> 28544 * </example> 28545 * 28546 * @element input 28547 * @param {string=} ngList optional delimiter that should be used to split the value. 28548 */ 28549var ngListDirective = function() { 28550 return { 28551 restrict: 'A', 28552 priority: 100, 28553 require: 'ngModel', 28554 link: function(scope, element, attr, ctrl) { 28555 // We want to control whitespace trimming so we use this convoluted approach 28556 // to access the ngList attribute, which doesn't pre-trim the attribute 28557 var ngList = element.attr(attr.$attr.ngList) || ', '; 28558 var trimValues = attr.ngTrim !== 'false'; 28559 var separator = trimValues ? trim(ngList) : ngList; 28560 28561 var parse = function(viewValue) { 28562 // If the viewValue is invalid (say required but empty) it will be `undefined` 28563 if (isUndefined(viewValue)) return; 28564 28565 var list = []; 28566 28567 if (viewValue) { 28568 forEach(viewValue.split(separator), function(value) { 28569 if (value) list.push(trimValues ? trim(value) : value); 28570 }); 28571 } 28572 28573 return list; 28574 }; 28575 28576 ctrl.$parsers.push(parse); 28577 ctrl.$formatters.push(function(value) { 28578 if (isArray(value)) { 28579 return value.join(ngList); 28580 } 28581 28582 return undefined; 28583 }); 28584 28585 // Override the standard $isEmpty because an empty array means the input is empty. 28586 ctrl.$isEmpty = function(value) { 28587 return !value || !value.length; 28588 }; 28589 } 28590 }; 28591}; 28592 28593/* global VALID_CLASS: true, 28594 INVALID_CLASS: true, 28595 PRISTINE_CLASS: true, 28596 DIRTY_CLASS: true, 28597 UNTOUCHED_CLASS: true, 28598 TOUCHED_CLASS: true, 28599*/ 28600 28601var VALID_CLASS = 'ng-valid', 28602 INVALID_CLASS = 'ng-invalid', 28603 PRISTINE_CLASS = 'ng-pristine', 28604 DIRTY_CLASS = 'ng-dirty', 28605 UNTOUCHED_CLASS = 'ng-untouched', 28606 TOUCHED_CLASS = 'ng-touched', 28607 PENDING_CLASS = 'ng-pending', 28608 EMPTY_CLASS = 'ng-empty', 28609 NOT_EMPTY_CLASS = 'ng-not-empty'; 28610 28611var ngModelMinErr = minErr('ngModel'); 28612 28613/** 28614 * @ngdoc type 28615 * @name ngModel.NgModelController 28616 * 28617 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 28618 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 28619 * is set. 28620 * @property {*} $modelValue The value in the model that the control is bound to. 28621 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 28622 the control reads value from the DOM. The functions are called in array order, each passing 28623 its return value through to the next. The last return value is forwarded to the 28624 {@link ngModel.NgModelController#$validators `$validators`} collection. 28625 28626Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 28627`$viewValue`}. 28628 28629Returning `undefined` from a parser means a parse error occurred. In that case, 28630no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 28631will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 28632is set to `true`. The parse error is stored in `ngModel.$error.parse`. 28633 28634 * 28635 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 28636 the model value changes. The functions are called in reverse array order, each passing the value through to the 28637 next. The last return value is used as the actual DOM value. 28638 Used to format / convert values for display in the control. 28639 * ```js 28640 * function formatter(value) { 28641 * if (value) { 28642 * return value.toUpperCase(); 28643 * } 28644 * } 28645 * ngModel.$formatters.push(formatter); 28646 * ``` 28647 * 28648 * @property {Object.<string, function>} $validators A collection of validators that are applied 28649 * whenever the model value changes. The key value within the object refers to the name of the 28650 * validator while the function refers to the validation operation. The validation operation is 28651 * provided with the model value as an argument and must return a true or false value depending 28652 * on the response of that validation. 28653 * 28654 * ```js 28655 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 28656 * var value = modelValue || viewValue; 28657 * return /[0-9]+/.test(value) && 28658 * /[a-z]+/.test(value) && 28659 * /[A-Z]+/.test(value) && 28660 * /\W+/.test(value); 28661 * }; 28662 * ``` 28663 * 28664 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 28665 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 28666 * is expected to return a promise when it is run during the model validation process. Once the promise 28667 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 28668 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 28669 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 28670 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 28671 * will only run once all synchronous validators have passed. 28672 * 28673 * Please note that if $http is used then it is important that the server returns a success HTTP response code 28674 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 28675 * 28676 * ```js 28677 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 28678 * var value = modelValue || viewValue; 28679 * 28680 * // Lookup user by username 28681 * return $http.get('/api/users/' + value). 28682 * then(function resolved() { 28683 * //username exists, this means validation fails 28684 * return $q.reject('exists'); 28685 * }, function rejected() { 28686 * //username does not exist, therefore this validation passes 28687 * return true; 28688 * }); 28689 * }; 28690 * ``` 28691 * 28692 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 28693 * view value has changed. It is called with no arguments, and its return value is ignored. 28694 * This can be used in place of additional $watches against the model value. 28695 * 28696 * @property {Object} $error An object hash with all failing validator ids as keys. 28697 * @property {Object} $pending An object hash with all pending validator ids as keys. 28698 * 28699 * @property {boolean} $untouched True if control has not lost focus yet. 28700 * @property {boolean} $touched True if control has lost focus. 28701 * @property {boolean} $pristine True if user has not interacted with the control yet. 28702 * @property {boolean} $dirty True if user has already interacted with the control. 28703 * @property {boolean} $valid True if there is no error. 28704 * @property {boolean}
28704 $invalid True if at least one error on the control. 28705 * @property {string} $name The name attribute of the control. 28706 * 28707 * @description 28708 * 28709 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 28710 * The controller contains services for data-binding, validation, CSS updates, and value formatting 28711 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 28712 * listening to DOM events. 28713 * Such DOM related logic should be provided by other directives which make use of 28714 * `NgModelController` for data-binding to control elements. 28715 * Angular provides this DOM logic for most {@link input `input`} elements. 28716 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 28717 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 28718 * 28719 * @example 28720 * ### Custom Control Example 28721 * This example shows how to use `NgModelController` with a custom control to achieve 28722 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 28723 * collaborate together to achieve the desired result. 28724 * 28725 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 28726 * contents be edited in place by the user. 28727 * 28728 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 28729 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 28730 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 28731 * that content using the `$sce` service. 28732 * 28733 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 28734 <file name="style.css"> 28735 [contenteditable] { 28736 border: 1px solid black; 28737 background-color: white; 28738 min-height: 20px; 28739 } 28740 28741 .ng-invalid { 28742 border: 1px solid red; 28743 } 28744 28745 </file> 28746 <file name="script.js"> 28747 angular.module('customControl', ['ngSanitize']). 28748 directive('contenteditable', ['$sce', function($sce) { 28749 return { 28750 restrict: 'A', // only activate on element attribute 28751 require: '?ngModel', // get a hold of NgModelController 28752 link: function(scope, element, attrs, ngModel) { 28753 if (!ngModel) return; // do nothing if no ng-model 28754 28755 // Specify how UI should be updated 28756 ngModel.$render = function() { 28757 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 28758 }; 28759 28760 // Listen for change events to enable binding 28761 element.on('blur keyup change', function() { 28762 scope.$evalAsync(read); 28763 }); 28764 read(); // initialize 28765 28766 // Write data to the model 28767 function read() { 28768 var html = element.html(); 28769 // When we clear the content editable the browser leaves a <br> behind 28770 // If strip-br attribute is provided then we strip this out 28771 if ( attrs.stripBr && html == '<br>' ) { 28772 html = ''; 28773 } 28774 ngModel.$setViewValue(html); 28775 } 28776 } 28777 }; 28778 }]); 28779 </file> 28780 <file name="index.html"> 28781 <form name="myForm"> 28782 <div contenteditable 28783 name="myWidget" ng-model="userContent" 28784 strip-br="true" 28785 required>Change me!</div> 28786 <span ng-show="myForm.myWidget.$error.required">Required!</span> 28787 <hr> 28788 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 28789 </form> 28790 </file> 28791 <file name="protractor.js" type="protractor"> 28792 it('should data-bind and become invalid', function() { 28793 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 28794 // SafariDriver can't handle contenteditable 28795 // and Firefox driver can't clear contenteditables very well 28796 return; 28797 } 28798 var contentEditable = element(by.css('[contenteditable]')); 28799 var content = 'Change me!'; 28800 28801 expect(contentEditable.getText()).toEqual(content); 28802 28803 contentEditable.clear(); 28804 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 28805 expect(contentEditable.getText()).toEqual(''); 28806 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 28807 }); 28808 </file> 28809 * </example> 28810 * 28811 * 28812 */ 28813var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 28814 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 28815 this.$viewValue = Number.NaN; 28816 this.$modelValue = Number.NaN; 28817 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 28818 this.$validators = {}; 28819 this.$asyncValidators = {}; 28820 this.$parsers = []; 28821 this.$formatters = []; 28822 this.$viewChangeListeners = []; 28823 this.$untouched = true; 28824 this.$touched = false;
28825 this.$pristine = true; 28826 this.$dirty = false; 28827 this.$valid = true; 28828 this.$invalid = false; 28829 this.$error = {}; // keep invalid keys here 28830 this.$$success = {}; // keep valid keys here 28831 this.$pending = undefined; // keep pending keys here 28832 this.$name = $interpolate($attr.name || '', false)($scope); 28833 this.$$parentForm = nullFormCtrl; 28834 28835 var parsedNgModel = $parse($attr.ngModel), 28836 parsedNgModelAssign = parsedNgModel.assign, 28837 ngModelGet = parsedNgModel, 28838 ngModelSet = parsedNgModelAssign, 28839 pendingDebounce = null, 28840 parserValid, 28841 ctrl = this; 28842 28843 this.$$setOptions = function(options) { 28844 ctrl.$options = options; 28845 if (options && options.getterSetter) { 28846 var invokeModelGetter = $parse($attr.ngModel + '()'), 28847 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 28848 28849 ngModelGet = function($scope) { 28850 var modelValue = parsedNgModel($scope); 28851 if (isFunction(modelValue)) { 28852 modelValue = invokeModelGetter($scope); 28853 } 28854 return modelValue; 28855 }; 28856 ngModelSet = function($scope, newValue) { 28857 if (isFunction(parsedNgModel($scope))) { 28858 invokeModelSetter($scope, {$$$p: newValue}); 28859 } else { 28860 parsedNgModelAssign($scope, newValue); 28861 } 28862 }; 28863 } else if (!parsedNgModel.assign) { 28864 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 28865 $attr.ngModel, startingTag($element)); 28866 } 28867 }; 28868 28869 /** 28870 * @ngdoc method 28871 * @name ngModel.NgModelController#$render 28872 * 28873 * @description 28874 * Called when the view needs to be updated. It is expected that the user of the ng-model 28875 * directive will implement this method. 28876 * 28877 * The `$render()` method is invoked in the following situations: 28878 * 28879 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 28880 * committed value then `$render()` is called to update the input control. 28881 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 28882 * the `$viewValue` are different from last time. 28883 * 28884 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 28885 * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue` 28886 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 28887 * invoked if you only change a property on the objects. 28888 */ 28889 this.$render = noop; 28890 28891 /** 28892 * @ngdoc method 28893 * @name ngModel.NgModelController#$isEmpty 28894 * 28895 * @description 28896 * This is called when we need to determine if the value of an input is empty. 28897 * 28898 * For instance, the required directive does this to work out if the input has data or not. 28899 * 28900 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 28901 * 28902 * You can override this for input directives whose concept of being empty is different from the 28903 * default. The `checkboxInputType` directive does this because in its case a value of `false` 28904 * implies empty. 28905 * 28906 * @param {*} value The value of the input to check for emptiness. 28907 * @returns {boolean} True if `value` is "empty". 28908 */ 28909 this.$isEmpty = function(value) { 28910 return isUndefined(value) || value === '' || value === null || value !== value; 28911 }; 28912 28913 this.$$updateEmptyClasses = function(value) { 28914 if (ctrl.$isEmpty(value)) { 28915 $animate.removeClass($element, NOT_EMPTY_CLASS); 28916 $animate.addClass($element, EMPTY_CLASS); 28917 } else { 28918 $animate.removeClass($element, EMPTY_CLASS); 28919 $animate.addClass($element, NOT_EMPTY_CLASS); 28920 } 28921 }; 28922 28923 28924 var currentValidationRunId = 0; 28925 28926 /** 28927 * @ngdoc method 28928 * @name ngModel.NgModelController#$setValidity 28929 * 28930 * @description 28931 * Change the validity state, and notify the form. 28932 * 28933 * This method can be called within $parsers/$formatters or a custom validation implementation. 28934 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 28935 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 28936 * 28937 * @param {string}
28937 validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 28938 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 28939 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. 28940 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 28941 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 28942 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 28943 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 28944 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 28945 * Skipped is used by Angular when validators do not run because of parse errors and 28946 * when `$asyncValidators` do not run because any of the `$validators` failed. 28947 */ 28948 addSetValidityMethod({ 28949 ctrl: this, 28950 $element: $element, 28951 set: function(object, property) { 28952 object[property] = true; 28953 }, 28954 unset: function(object, property) { 28955 delete object[property]; 28956 }, 28957 $animate: $animate 28958 }); 28959 28960 /** 28961 * @ngdoc method 28962 * @name ngModel.NgModelController#$setPristine 28963 * 28964 * @description 28965 * Sets the control to its pristine state. 28966 * 28967 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 28968 * state (`ng-pristine` class). A model is considered to be pristine when the control 28969 * has not been changed from when first compiled. 28970 */ 28971 this.$setPristine = function() { 28972 ctrl.$dirty = false; 28973 ctrl.$pristine = true; 28974 $animate.removeClass($element, DIRTY_CLASS); 28975 $animate.addClass($element, PRISTINE_CLASS); 28976 }; 28977 28978 /** 28979 * @ngdoc method 28980 * @name ngModel.NgModelController#$setDirty 28981 * 28982 * @description 28983 * Sets the control to its dirty state. 28984 * 28985 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 28986 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 28987 * from when first compiled. 28988 */ 28989 this.$setDirty = function() { 28990 ctrl.$dirty = true; 28991 ctrl.$pristine = false; 28992 $animate.removeClass($element, PRISTINE_CLASS); 28993 $animate.addClass($element, DIRTY_CLASS); 28994 ctrl.$$parentForm.$setDirty(); 28995 }; 28996 28997 /** 28998 * @ngdoc method 28999 * @name ngModel.NgModelController#$setUntouched 29000 * 29001 * @description 29002 * Sets the control to its untouched state. 29003 * 29004 * This method can be called to remove the `ng-touched` class and set the control to its 29005 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 29006 * by default, however this function can be used to restore that state if the model has 29007 * already been touched by the user. 29008 */ 29009 this.$setUntouched = function() { 29010 ctrl.$touched = false; 29011 ctrl.$untouched = true; 29012 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 29013 }; 29014 29015 /** 29016 * @ngdoc method 29017 * @name ngModel.NgModelController#$setTouched 29018 * 29019 * @description 29020 * Sets the control to its touched state. 29021 * 29022 * This method can be called to remove the `ng-untouched` class and set the control to its 29023 * touched state (`ng-touched` class). A model is considered to be touched when the user has 29024 * first focused the control element and then shifted focus away from the control (blur event). 29025 */ 29026 this.$setTouched = function() { 29027 ctrl.$touched = true; 29028 ctrl.$untouched = false; 29029 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 29030 }; 29031 29032 /** 29033 * @ngdoc method 29034 * @name ngModel.NgModelController#$rollbackViewValue 29035 * 29036 * @description 29037 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 29038 * which may be caused by a pending debounced event or because the input is waiting for a some 29039 * future event. 29040 * 29041 * If you have an input that uses `ng-model-options` to set up debounced updates or updates that 29042 * depend on special events such as blur, you can have a situation where there is a period when 29043 * the `$viewValue` is out of sync with the ngModel's `$modelValue`. 29044 * 29045 * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update 29046 * and reset the input to the last committed view value. 29047 * 29048 * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue` 29049 * programmatically before these debounced/future events have resolved/occurred, because Angular's 29050 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 29051 * 29052 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 29053 * input which may have such events pending. This is important in order to make sure that the 29054 * input field will be updated with the new model value and any pending operations are cancelled. 29055 * 29056 * <example name="ng-model-cancel-update" module="cancel-update-example"> 29057 * <file name="app.js">
29058 * angular.module('cancel-update-example', []) 29059 * 29060 * .controller('CancelUpdateController', ['$scope', function($scope) { 29061 * $scope.model = {}; 29062 * 29063 * $scope.setEmpty = function(e, value, rollback) { 29064 * if (e.keyCode == 27) { 29065 * e.preventDefault(); 29066 * if (rollback) { 29067 * $scope.myForm[value].$rollbackViewValue(); 29068 * } 29069 * $scope.model[value] = ''; 29070 * } 29071 * }; 29072 * }]); 29073 * </file> 29074 * <file name="index.html"> 29075 * <div ng-controller="CancelUpdateController"> 29076 * <p>Both of these inputs are only updated if they are blurred. Hitting escape should 29077 * empty them. Follow these steps and observe the difference:</p> 29078 * <ol> 29079 * <li>Type something in the input. You will see that the model is not yet updated</li> 29080 * <li>Press the Escape key. 29081 * <ol> 29082 * <li> In the first example, nothing happens, because the model is already '', and no 29083 * update is detected. If you blur the input, the model will be set to the current view. 29084 * </li> 29085 * <li> In the second example, the pending update is cancelled, and the input is set back 29086 * to the last committed view value (''). Blurring the input does nothing. 29087 * </li> 29088 * </ol> 29089 * </li> 29090 * </ol> 29091 * 29092 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 29093 * <div> 29094 * <p id="inputDescription1">Without $rollbackViewValue():</p> 29095 * <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" 29096 * ng-keydown="setEmpty($event, 'value1')"> 29097 * value1: "{{ model.value1 }}" 29098 * </div> 29099 * 29100 * <div> 29101 * <p id="inputDescription2">With $rollbackViewValue():</p> 29102 * <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" 29103 * ng-keydown="setEmpty($event, 'value2', true)"> 29104 * value2: "{{ model.value2 }}" 29105 * </div> 29106 * </form> 29107 * </div> 29108 * </file> 29109 <file name="style.css"> 29110 div { 29111 display: table-cell; 29112 } 29113 div:nth-child(1) { 29114 padding-right: 30px; 29115 } 29116 29117 </file> 29118 * </example> 29119 */ 29120 this.$rollbackViewValue = function() { 29121 $timeout.cancel(pendingDebounce); 29122 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 29123 ctrl.$render(); 29124 }; 29125 29126 /** 29127 * @ngdoc method 29128 * @name ngModel.NgModelController#$validate 29129 * 29130 * @description 29131 * Runs each of the registered validators (first synchronous validators and then 29132 * asynchronous validators). 29133 * If the validity changes to invalid, the model will be set to `undefined`, 29134 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 29135 * If the validity changes to valid, it will set the model to the last available valid 29136 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 29137 */ 29138 this.$validate = function() { 29139 // ignore $validate before model is initialized 29140 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 29141 return; 29142 } 29143 29144 var viewValue = ctrl.$$lastCommittedViewValue; 29145 // Note: we use the $$rawModelValue as $modelValue might have been 29146 // set to undefined during a view -> model update that found validation 29147 // errors. We can't parse the view here, since that could change 29148 // the model although neither viewValue nor the model on the scope changed 29149 var modelValue = ctrl.$$rawModelValue; 29150 29151 var prevValid = ctrl.$valid; 29152 var prevModelValue = ctrl.$modelValue; 29153 29154 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 29155 29156 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 29157 // If there was no change in validity, don't update the model 29158 // This prevents changing an invalid modelValue to undefined 29159 if (!allowInvalid && prevValid !== allValid) { 29160 // Note: Don't check ctrl.$valid here, as we could have 29161 // external validators (e.g. calculated on the server), 29162 // that just call $setValidity and need the model value 29163 // to calculate their validity. 29164 ctrl.$modelValue = allValid ? modelValue : undefined; 29165 29166 if (ctrl.$modelValue !== prevModelValue) { 29167 ctrl.$$writeModelToScope(); 29168 } 29169 } 29170 }); 29171 29172 }; 29173 29174 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 29175 currentValidationRunId++; 29176 var localValidationRunId = currentValidationRunId; 29177 29178 // check parser error 29179 if (!processParseErrors()) { 29180 validationDone(false); 29181 return; 29182 } 29183 if (!processSyncValidators()) { 29184 validationDone(false); 29185 return; 29186 } 29187 processAsyncValidators(); 29188 29189 function processParseErrors() { 29190 var errorKey = ctrl.$$parserName || 'parse'; 29191 if (isUndefined(parserValid)) {
29192 setValidity(errorKey, null); 29193 } else { 29194 if (!parserValid) { 29195 forEach(ctrl.$validators, function(v, name) { 29196 setValidity(name, null); 29197 }); 29198 forEach(ctrl.$asyncValidators, function(v, name) { 29199 setValidity(name, null); 29200 }); 29201 } 29202 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 29203 setValidity(errorKey, parserValid); 29204 return parserValid; 29205 } 29206 return true; 29207 } 29208 29209 function processSyncValidators() { 29210 var syncValidatorsValid = true; 29211 forEach(ctrl.$validators, function(validator, name) { 29212 var result = validator(modelValue, viewValue); 29213 syncValidatorsValid = syncValidatorsValid && result; 29214 setValidity(name, result); 29215 }); 29216 if (!syncValidatorsValid) { 29217 forEach(ctrl.$asyncValidators, function(v, name) { 29218 setValidity(name, null); 29219 }); 29220 return false; 29221 } 29222 return true; 29223 } 29224 29225 function processAsyncValidators() { 29226 var validatorPromises = []; 29227 var allValid = true; 29228 forEach(ctrl.$asyncValidators, function(validator, name) { 29229 var promise = validator(modelValue, viewValue); 29230 if (!isPromiseLike(promise)) { 29231 throw ngModelMinErr('nopromise', 29232 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 29233 } 29234 setValidity(name, undefined); 29235 validatorPromises.push(promise.then(function() { 29236 setValidity(name, true); 29237 }, function() { 29238 allValid = false; 29239 setValidity(name, false); 29240 })); 29241 }); 29242 if (!validatorPromises.length) { 29243 validationDone(true); 29244 } else { 29245 $q.all(validatorPromises).then(function() { 29246 validationDone(allValid); 29247 }, noop); 29248 } 29249 } 29250 29251 function setValidity(name, isValid) { 29252 if (localValidationRunId === currentValidationRunId) { 29253 ctrl.$setValidity(name, isValid); 29254 } 29255 } 29256 29257 function validationDone(allValid) { 29258 if (localValidationRunId === currentValidationRunId) { 29259 29260 doneCallback(allValid); 29261 } 29262 } 29263 }; 29264 29265 /** 29266 * @ngdoc method 29267 * @name ngModel.NgModelController#$commitViewValue 29268 * 29269 * @description 29270 * Commit a pending update to the `$modelValue`. 29271 * 29272 * Updates may be pending by a debounced event or because the input is waiting for a some future 29273 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 29274 * usually handles calling this in response to input events. 29275 */ 29276 this.$commitViewValue = function() { 29277 var viewValue = ctrl.$viewValue; 29278 29279 $timeout.cancel(pendingDebounce); 29280 29281 // If the view value has not changed then we should just exit, except in the case where there is 29282 // a native validator on the element. In this case the validation state may have changed even though 29283 // the viewValue has stayed empty. 29284 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 29285 return; 29286 } 29287 ctrl.$$updateEmptyClasses(viewValue); 29288 ctrl.$$lastCommittedViewValue = viewValue; 29289 29290 // change to dirty 29291 if (ctrl.$pristine) { 29292 this.$setDirty(); 29293 } 29294 this.$$parseAndValidate(); 29295 }; 29296 29297 this.$$parseAndValidate = function() { 29298 var viewValue = ctrl.$$lastCommittedViewValue; 29299 var modelValue = viewValue; 29300 parserValid = isUndefined(modelValue) ? undefined : true; 29301 29302 if (parserValid) { 29303 for (var i = 0; i < ctrl.$parsers.length; i++) { 29304 modelValue = ctrl.$parsers[i](modelValue); 29305 if (isUndefined(modelValue)) { 29306 parserValid = false; 29307 break; 29308 } 29309 } 29310 } 29311 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 29312 // ctrl.$modelValue has not been touched yet... 29313 ctrl.$modelValue = ngModelGet($scope); 29314 } 29315 var prevModelValue = ctrl.$modelValue; 29316 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 29317 ctrl.$$rawModelValue = modelValue; 29318 29319 if (allowInvalid) { 29320 ctrl.$modelValue = modelValue; 29321 writeToModelIfNeeded(); 29322 } 29323 29324 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 29325 // This can happen if e.g. $setViewValue is called from inside a parser 29326 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 29327 if (!allowInvalid) { 29328 // Note: Don't check ctrl.$valid here, as we could have 29329 // external validators (e.g. calculated on the server), 29330 // that just call $setValidity and need the model value 29331 // to calculate their validity. 29332 ctrl.$modelValue = allValid ? modelValue : undefined; 29333 writeToModelIfNeeded(); 29334 } 29335 }); 29336 29337 function writeToModelIfNeeded() { 29338 if (ctrl.$modelValue !== prevModelValue) { 29339 ctrl.$$writeModelToScope(); 29340 } 29341 } 29342 }; 29343 29344 this.$$writeModelToScope = function() { 29345 ngModelSet($scope, ctrl.$modelValue);
29346 forEach(ctrl.$viewChangeListeners, function(listener) { 29347 try { 29348 listener(); 29349 } catch (e) { 29350 $exceptionHandler(e); 29351 } 29352 }); 29353 }; 29354 29355 /** 29356 * @ngdoc method 29357 * @name ngModel.NgModelController#$setViewValue 29358 * 29359 * @description 29360 * Update the view value. 29361 * 29362 * This method should be called when a control wants to change the view value; typically, 29363 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 29364 * directive calls it when the value of the input changes and {@link ng.directive:select select} 29365 * calls it when an option is selected. 29366 * 29367 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 29368 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 29369 * value sent directly for processing, finally to be applied to `$modelValue` and then the 29370 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, 29371 * in the `$viewChangeListeners` list, are called. 29372 * 29373 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 29374 * and the `default` trigger is not listed, all those actions will remain pending until one of the 29375 * `updateOn` events is triggered on the DOM element. 29376 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 29377 * directive is used with a custom debounce for this particular event. 29378 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 29379 * is specified, once the timer runs out. 29380 * 29381 * When used with standard inputs, the view value will always be a string (which is in some cases 29382 * parsed into another type, such as a `Date` object for `input[date]`.) 29383 * However, custom controls might also pass objects to this method. In this case, we should make 29384 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 29385 * perform a deep watch of objects, it only looks for a change of identity. If you only change 29386 * the property of the object then ngModel will not realize that the object has changed and 29387 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 29388 * not change properties of the copy once it has been passed to `$setViewValue`. 29389 * Otherwise you may cause the model value on the scope to change incorrectly. 29390 * 29391 * <div class="alert alert-info"> 29392 * In any case, the value passed to the method should always reflect the current value 29393 * of the control. For example, if you are calling `$setViewValue` for an input element, 29394 * you should pass the input DOM value. Otherwise, the control and the scope model become 29395 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 29396 * the control's DOM value in any way. If we want to change the control's DOM value 29397 * programmatically, we should update the `ngModel` scope expression. Its new value will be 29398 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 29399 * to update the DOM, and finally call `$validate` on it. 29400 * </div> 29401 * 29402 * @param {*} value value from the view. 29403 * @param {string} trigger Event that triggered the update. 29404 */ 29405 this.$setViewValue = function(value, trigger) { 29406 ctrl.$viewValue = value; 29407 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 29408 ctrl.$$debounceViewValueCommit(trigger); 29409 } 29410 }; 29411 29412 this.$$debounceViewValueCommit = function(trigger) { 29413 var debounceDelay = 0, 29414 options = ctrl.$options, 29415 debounce; 29416 29417 if (options && isDefined(options.debounce)) { 29418 debounce = options.debounce; 29419 if (isNumber(debounce)) { 29420 debounceDelay = debounce; 29421 } else if (isNumber(debounce[trigger])) { 29422 debounceDelay = debounce[trigger]; 29423 } else if (isNumber(debounce['default'])) { 29424 debounceDelay = debounce['default']; 29425 } 29426 } 29427 29428 $timeout.cancel(pendingDebounce); 29429 if (debounceDelay) { 29430 pendingDebounce = $timeout(function() { 29431 ctrl.$commitViewValue(); 29432 }, debounceDelay); 29433 } else if ($rootScope.$$phase) { 29434 ctrl.$commitViewValue(); 29435 } else { 29436 $scope.$apply(function() { 29437 ctrl.$commitViewValue(); 29438 }); 29439 } 29440 }; 29441 29442 // model -> value 29443 // Note: we cannot use a normal scope.$watch as we want to detect the following: 29444 // 1. scope value is 'a' 29445 // 2. user enters 'b' 29446 // 3. ng-change kicks in and reverts scope value to 'a' 29447 // ->
29447 scope value did not change since the last digest as 29448 // ng-change executes in apply phase 29449 // 4. view should be changed back to 'a' 29450 $scope.$watch(function ngModelWatch() { 29451 var modelValue = ngModelGet($scope); 29452 29453 // if scope model value and ngModel value are out of sync 29454 // TODO(perf): why not move this to the action fn? 29455 if (modelValue !== ctrl.$modelValue && 29456 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 29457 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 29458 ) { 29459 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 29460 parserValid = undefined; 29461 29462 var formatters = ctrl.$formatters, 29463 idx = formatters.length; 29464 29465 var viewValue = modelValue; 29466 while (idx--) { 29467 viewValue = formatters[idx](viewValue); 29468 } 29469 if (ctrl.$viewValue !== viewValue) { 29470 ctrl.$$updateEmptyClasses(viewValue); 29471 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 29472 ctrl.$render(); 29473 29474 ctrl.$$runValidators(modelValue, viewValue, noop); 29475 } 29476 } 29477 29478 return modelValue; 29479 }); 29480}]; 29481 29482 29483/** 29484 * @ngdoc directive 29485 * @name ngModel 29486 * 29487 * @element input 29488 * @priority 1 29489 * 29490 * @description 29491 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 29492 * property on the scope using {@link ngModel.NgModelController NgModelController}, 29493 * which is created and exposed by this directive. 29494 * 29495 * `ngModel` is responsible for: 29496 * 29497 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 29498 * require. 29499 * - Providing validation behavior (i.e. required, number, email, url). 29500 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 29501 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, 29502 * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations. 29503 * - Registering the control with its parent {@link ng.directive:form form}. 29504 * 29505 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 29506 * current scope. If the property doesn't already exist on this scope, it will be created 29507 * implicitly and added to the scope. 29508 * 29509 * For best practices on using `ngModel`, see: 29510 * 29511 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 29512 * 29513 * For basic examples, how to use `ngModel`, see: 29514 * 29515 * - {@link ng.directive:input input} 29516 * - {@link input[text] text} 29517 * - {@link input[checkbox] checkbox} 29518 * - {@link input[radio] radio} 29519 * - {@link input[number] number} 29520 * - {@link input[email] email} 29521 * - {@link input[url] url} 29522 * - {@link input[date] date} 29523 * - {@link input[datetime-local] datetime-local} 29524 * - {@link input[time] time} 29525 * - {@link input[month] month} 29526 * - {@link input[week] week} 29527 * - {@link ng.directive:select select} 29528 * - {@link ng.directive:textarea textarea} 29529 * 29530 * # Complex Models (objects or collections) 29531 * 29532 * By default, `ngModel` watches the model by reference, not value. This is important to know when 29533 * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the 29534 * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered. 29535 * 29536 * The model must be assigned an entirely new object or collection before a re-rendering will occur. 29537 * 29538 * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression 29539 * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or 29540 * if the select is given the `multiple` attribute. 29541 * 29542 * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the 29543 * first level of the object (or only changing the properties of an item in the collection if it's an array) will still 29544 * not trigger a re-rendering of the model. 29545 * 29546 * # CSS classes 29547 * The following CSS classes are added and removed on the associated input/select/textarea element 29548 * depending on the validity of the model. 29549 * 29550 * - `ng-valid`: the model is valid
29551 * - `ng-invalid`: the model is invalid 29552 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 29553 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 29554 * - `ng-pristine`: the control hasn't been interacted with yet 29555 * - `ng-dirty`: the control has been interacted with 29556 * - `ng-touched`: the control has been blurred 29557 * - `ng-untouched`: the control hasn't been blurred 29558 * - `ng-pending`: any `$asyncValidators` are unfulfilled 29559 * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined 29560 * by the {@link ngModel.NgModelController#$isEmpty} method 29561 * - `ng-not-empty`: the view contains a non-empty value 29562 * 29563 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 29564 * 29565 * ## Animation Hooks 29566 * 29567 * Animations within models are triggered when any of the associated CSS classes are added and removed 29568 * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`, 29569 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 29570 * The animations that are triggered within ngModel are similar to how they work in ngClass and 29571 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 29572 * 29573 * The following example shows a simple way to utilize CSS transitions to style an input element 29574 * that has been rendered as invalid after it has been validated: 29575 * 29576 * <pre> 29577 * //be sure to include ngAnimate as a module to hook into more 29578 * //advanced animations 29579 * .my-input { 29580 * transition:0.5s linear all; 29581 * background: white; 29582 * } 29583 * .my-input.ng-invalid { 29584 * background: red; 29585 * color:white; 29586 * } 29587 * </pre> 29588 * 29589 * @example 29590 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 29591 <file name="index.html"> 29592 <script> 29593 angular.module('inputExample', []) 29594 .controller('ExampleController', ['$scope', function($scope) { 29595 $scope.val = '1'; 29596 }]); 29597 </script> 29598 <style> 29599 .my-input { 29600 transition:all linear 0.5s; 29601 background: transparent; 29602 } 29603 .my-input.ng-invalid { 29604 color:white; 29605 background: red; 29606 } 29607 </style> 29608 <p id="inputDescription"> 29609 Update input to see transitions when valid/invalid. 29610 Integer is a valid value. 29611 </p>
29612 <form name="testForm" ng-controller="ExampleController"> 29613 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 29614 aria-describedby="inputDescription" /> 29615 </form> 29616 </file> 29617 * </example> 29618 * 29619 * ## Binding to a getter/setter 29620 * 29621 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 29622 * function that returns a representation of the model when called with zero arguments, and sets 29623 * the internal state of a model when called with an argument. It's sometimes useful to use this 29624 * for models that have an internal representation that's different from what the model exposes 29625 * to the view. 29626 * 29627 * <div class="alert alert-success"> 29628 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 29629 * frequently than other parts of your code. 29630 * </div> 29631 * 29632 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 29633 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 29634 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 29635 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 29636 * 29637 * The following example shows how to use `ngModel` with a getter/setter: 29638 * 29639 * @example 29640 * <example name="ngModel-getter-setter" module="getterSetterExample"> 29641 <file name="index.html">
29642 <div ng-controller="ExampleController"> 29643 <form name="userForm"> 29644 <label>Name: 29645 <input type="text" name="userName" 29646 ng-model="user.name" 29647 ng-model-options="{ getterSetter: true }" /> 29648 </label> 29649 </form> 29650 <pre>user.name = <span ng-bind="user.name()"></span></pre> 29651 </div> 29652 </file> 29653 <file name="app.js"> 29654 angular.module('getterSetterExample', []) 29655 .controller('ExampleController', ['$scope', function($scope) { 29656 var _name = 'Brian'; 29657 $scope.user = { 29658 name: function(newName) { 29659 // Note that newName can be undefined for two reasons: 29660 // 1. Because it is called as a getter and thus called with no arguments 29661 // 2. Because the property should actually be set to undefined. This happens e.g. if the 29662 // input is invalid 29663 return arguments.length ? (_name = newName) : _name; 29664 } 29665 }; 29666 }]); 29667 </file> 29668 * </example> 29669 */ 29670var ngModelDirective = ['$rootScope', function($rootScope) { 29671 return { 29672 restrict: 'A', 29673 require: ['ngModel', '^?form', '^?ngModelOptions'], 29674 controller: NgModelController, 29675 // Prelink needs to run before any input directive 29676 // so that we can set the NgModelOptions in NgModelController 29677 // before anyone else uses it. 29678 priority: 1, 29679 compile: function ngModelCompile(element) { 29680 // Setup initial state of the control 29681 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 29682 29683 return { 29684 pre: function ngModelPreLink(scope, element, attr, ctrls) { 29685 var modelCtrl = ctrls[0], 29686 formCtrl = ctrls[1] || modelCtrl.$$parentForm; 29687 29688 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 29689 29690 // notify others, especially parent forms 29691 formCtrl.$addControl(modelCtrl); 29692 29693 attr.$observe('name', function(newValue) { 29694 if (modelCtrl.$name !== newValue) { 29695 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 29696 } 29697 }); 29698 29699 scope.$on('$destroy', function() { 29700 modelCtrl.$$parentForm.$removeControl(modelCtrl); 29701 }); 29702 }, 29703 post: function ngModelPostLink(scope, element, attr, ctrls) { 29704 var modelCtrl = ctrls[0]; 29705 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 29706 element.on(modelCtrl.$options.updateOn, function(ev) { 29707 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 29708 }); 29709 } 29710 29711 element.on('blur', function() { 29712 if (modelCtrl.$touched) return; 29713 29714 if ($rootScope.$$phase) { 29715 scope.$evalAsync(modelCtrl.$setTouched); 29716 } else { 29717 scope.$apply(modelCtrl.$setTouched); 29718 } 29719 }); 29720 } 29721 }; 29722 } 29723 }; 29724}]; 29725 29726var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 29727 29728/** 29729 * @ngdoc directive 29730 * @name ngModelOptions 29731 * 29732 * @description 29733 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 29734 * events that will trigger a model update and/or a debouncing delay so that the actual update only 29735 * takes place when a timer expires; this timer will be reset after another change takes place. 29736 * 29737 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 29738 * be different from the value in the actual model. This means that if you update the model you 29739 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 29740 * order to make sure it is synchronized with the model and that any debounced action is canceled. 29741 * 29742 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 29743 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 29744 * important because `form` controllers are published to the related scope under the name in their 29745 * `name` attribute. 29746 * 29747 * Any pending changes will take place immediately when an enclosing form is submitted via the 29748 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 29749 * to have access to the updated model. 29750 * 29751 * `ngModelOptions` has an effect on the element it's declared on and its desce
29751ndants. 29752 * 29753 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 29754 * - `updateOn`: string specifying which event should the input be bound to. You can set several 29755 * events using an space delimited list. There is a special event called `default` that 29756 * matches the default events belonging of the control. 29757 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 29758 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 29759 * custom value for each event. For example: 29760 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 29761 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 29762 * not validate correctly instead of the default behavior of setting the model to undefined. 29763 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 29764 `ngModel` as getters/setters. 29765 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 29766 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 29767 * continental US time zone abbreviations, but for general use, use a time zone offset, for 29768 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 29769 * If not specified, the timezone of the browser will be used. 29770 * 29771 * @example 29772 29773 The following example shows how to override immediate updates. Changes on the inputs within the 29774 form will update the model only when the control loses focus (blur event). If `escape` key is 29775 pressed while the input field is focused, the value is reset to the value in the current model. 29776 29777 <example name="ngModelOptions-directive-blur" module="optionsExample"> 29778 <file name="index.html">
29779 <div ng-controller="ExampleController"> 29780 <form name="userForm"> 29781 <label>Name: 29782 <input type="text" name="userName" 29783 ng-model="user.name" 29784 ng-model-options="{ updateOn: 'blur' }" 29785 ng-keyup="cancel($event)" /> 29786 </label><br /> 29787 <label>Other data: 29788 <input type="text" ng-model="user.data" /> 29789 </label><br /> 29790 </form> 29791 <pre>user.name = <span ng-bind="user.name"></span></pre> 29792 <pre>user.data = <span ng-bind="user.data"></span></pre> 29793 </div> 29794 </file> 29795 <file name="app.js"> 29796 angular.module('optionsExample', []) 29797 .controller('ExampleController', ['$scope', function($scope) { 29798 $scope.user = { name: 'John', data: '' }; 29799 29800 $scope.cancel = function(e) { 29801 if (e.keyCode == 27) { 29802 $scope.userForm.userName.$rollbackViewValue(); 29803 } 29804 }; 29805 }]); 29806 </file> 29807 <file name="protractor.js" type="protractor"> 29808 var model = element(by.binding('user.name')); 29809 var input = element(by.model('user.name')); 29810 var other = element(by.model('user.data')); 29811 29812 it('should allow custom events', function() { 29813 input.sendKeys(' Doe'); 29814 input.click(); 29815 expect(model.getText()).toEqual('John'); 29816 other.click(); 29817 expect(model.getText()).toEqual('John Doe'); 29818 }); 29819 29820 it('should $rollbackViewValue when model changes', function() { 29821 input.sendKeys(' Doe'); 29822 expect(input.getAttribute('value')).toEqual('John Doe'); 29823 input.sendKeys(protractor.Key.ESCAPE); 29824 expect(input.getAttribute('value')).toEqual('John'); 29825 other.click(); 29826 expect(model.getText()).toEqual('John'); 29827 }); 29828 </file> 29829 </example> 29830 29831 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 29832 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 29833 29834 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 29835 <file name="index.html">
29836 <div ng-controller="ExampleController"> 29837 <form name="userForm"> 29838 <label>Name: 29839 <input type="text" name="userName" 29840 ng-model="user.name" 29841 ng-model-options="{ debounce: 1000 }" /> 29842 </label> 29843 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 29844 <br /> 29845 </form> 29846 <pre>user.name = <span ng-bind="user.name"></span></pre> 29847 </div> 29848 </file> 29849 <file name="app.js"> 29850 angular.module('optionsExample', []) 29851 .controller('ExampleController', ['$scope', function($scope) { 29852 $scope.user = { name: 'Igor' }; 29853 }]); 29854 </file> 29855 </example> 29856 29857 This one shows how to bind to getter/setters: 29858 29859 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 29860 <file name="index.html"> 29861 <div ng-controller="ExampleController"> 29862 <form name="userForm"> 29863 <label>Name: 29864 <input type="text" name="userName" 29865 ng-model="user.name" 29866 ng-model-options="{ getterSetter: true }" /> 29867 </label> 29868 </form> 29869 <pre>user.name = <span ng-bind="user.name()"></span></pre> 29870 </div> 29871 </file> 29872 <file name="app.js"> 29873 angular.module('getterSetterExample', []) 29874 .controller('ExampleController', ['$scope', function($scope) { 29875 var _name = 'Brian'; 29876 $scope.user = { 29877 name: function(newName) { 29878 // Note that newName can be undefined for two reasons: 29879 // 1. Because it is called as a getter and thus called with no arguments 29880 // 2. Because the property should actually be set to undefined. This happens e.g. if the 29881 // input is invalid 29882 return arguments.length ? (_name = newName) : _name; 29883 } 29884 }; 29885 }]); 29886 </file> 29887 </example> 29888 */ 29889var ngModelOptionsDirective = function() { 29890 return { 29891 restrict: 'A', 29892 controller: ['$scope', '$attrs', function($scope, $attrs) { 29893 var that = this; 29894 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 29895 // Allow adding/overriding bound events 29896 if (isDefined(this.$options.updateOn)) { 29897 this.$options.updateOnDefault = false;
29898 // extract "default" pseudo-event from list of events that can trigger a model update 29899 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 29900 that.$options.updateOnDefault = true; 29901 return ' '; 29902 })); 29903 } else { 29904 this.$options.updateOnDefault = true; 29905 } 29906 }] 29907 }; 29908}; 29909 29910 29911 29912// helper methods 29913function addSetValidityMethod(context) { 29914 var ctrl = context.ctrl, 29915 $element = context.$element, 29916 classCache = {}, 29917 set = context.set, 29918 unset = context.unset, 29919 $animate = context.$animate; 29920 29921 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 29922 29923 ctrl.$setValidity = setValidity; 29924 29925 function setValidity(validationErrorKey, state, controller) { 29926 if (isUndefined(state)) { 29927 createAndSet('$pending', validationErrorKey, controller); 29928 } else { 29929 unsetAndCleanup('$pending', validationErrorKey, controller); 29930 } 29931 if (!isBoolean(state)) { 29932 unset(ctrl.$error, validationErrorKey, controller); 29933 unset(ctrl.$$success, validationErrorKey, controller); 29934 } else { 29935 if (state) { 29936 unset(ctrl.$error, validationErrorKey, controller); 29937 set(ctrl.$$success, validationErrorKey, controller); 29938 } else { 29939 set(ctrl.$error, validationErrorKey, controller); 29940 unset(ctrl.$$success, validationErrorKey, controller); 29941 } 29942 } 29943 if (ctrl.$pending) { 29944 cachedToggleClass(PENDING_CLASS, true); 29945 ctrl.$valid = ctrl.$invalid = undefined; 29946 toggleValidationCss('', null); 29947 } else { 29948 cachedToggleClass(PENDING_CLASS, false); 29949 ctrl.$valid = isObjectEmpty(ctrl.$error); 29950 ctrl.$invalid = !ctrl.$valid; 29951 toggleValidationCss('', ctrl.$valid); 29952 } 29953 29954 // re-read the state as the set/unset methods could have 29955 // combined state in ctrl.$error[validationError] (used for forms), 29956 // where setting/unsetting only increments/decrements the value, 29957 // and does not replace it. 29958 var combinedState; 29959 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 29960 combinedState = undefined; 29961 } else if (ctrl.$error[validationErrorKey]) { 29962 combinedState = false; 29963 } else if (ctrl.$$success[validationErrorKey]) { 29964 combinedState = true; 29965 } else { 29966 combinedState = null; 29967 } 29968
29969 toggleValidationCss(validationErrorKey, combinedState); 29970 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 29971 } 29972 29973 function createAndSet(name, value, controller) { 29974 if (!ctrl[name]) { 29975 ctrl[name] = {}; 29976 } 29977 set(ctrl[name], value, controller); 29978 } 29979 29980 function unsetAndCleanup(name, value, controller) { 29981 if (ctrl[name]) { 29982 unset(ctrl[name], value, controller); 29983 } 29984 if (isObjectEmpty(ctrl[name])) { 29985 ctrl[name] = undefined; 29986 } 29987 } 29988 29989 function cachedToggleClass(className, switchValue) { 29990 if (switchValue && !classCache[className]) { 29991 $animate.addClass($element, className); 29992 classCache[className] = true; 29993 } else if (!switchValue && classCache[className]) { 29994 $animate.removeClass($element, className); 29995 classCache[className] = false; 29996 } 29997 } 29998 29999 function toggleValidationCss(validationErrorKey, isValid) { 30000 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 30001 30002 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 30003 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 30004 } 30005} 30006 30007function isObjectEmpty(obj) { 30008 if (obj) { 30009 for (var prop in obj) { 30010 if (obj.hasOwnProperty(prop)) { 30011 return false; 30012 } 30013 } 30014 } 30015 return true; 30016} 30017 30018/** 30019 * @ngdoc directive 30020 * @name ngNonBindable 30021 * @restrict AC 30022 * @priority 1000 30023 * 30024 * @description 30025 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 30026 * DOM element. This is useful if the element contains what appears to be Angular directives and 30027 * bindings but which should be ignored by Angular. This could be the case if you have a site that 30028 * displays snippets of code, for instance. 30029 * 30030 * @element ANY 30031 * 30032 * @example 30033 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 30034 * but the one wrapped in `ngNonBindable` is left alone. 30035 * 30036 * @example 30037 <example> 30038 <file name="index.html"> 30039 <div>Normal: {{1 + 2}}</div> 30040 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 30041 </file> 30042 <file name="protractor.js" type="protractor"> 30043 it('should check ng-non-bindable', function() { 30044 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 30045 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 30046 }); 30047 </file> 30048 </example> 30049 */ 30050var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 30051 30052/* global jqLiteRemove */ 30053 30054var ngOptionsMinErr = minErr('ngOptions'); 30055 30056/** 30057 * @ngdoc directive 30058 * @name ngOptions 30059 * @restrict A 30060 * 30061 * @description 30062 * 30063 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 30064 * elements for the `<select>` element using the array or object obtained by evaluating the 30065 * `ngOptions` comprehension expression. 30066 * 30067 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 30068 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 30069 * increasing speed by not creating a new scope for each repeated instance, as well as providing 30070 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 30071 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 30072 * to a non-string value. This is because an option element can only be bound to string values at 30073 * present. 30074 * 30075 * When an item in the `<select>` menu is selected, the array element or object property 30076 * represented by the selected option will be bound to the model identified by the `ngModel` 30077 * directive. 30078 * 30079 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 30080 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 30081 * option. See example below for demonstration. 30082 * 30083 * ## Complex Models (objects or collections) 30084 * 30085 * By default, `ngModel` watches the model by reference, not value. This is important to know when 30086 * binding the select to a model that is an object or a collection. 30087 * 30088 * One issue occurs if you want to preselect an option. For example, if you set 30089 * the model to an object that is equal to an object in your collection, `ngOptions` won't be able to set the selection, 30090 * because the objects are not identical. So by default, you should always reference the item in y
30090our collection 30091 * for preselections, e.g.: `$scope.selected = $scope.collection[3]`. 30092 * 30093 * Another solution is to use a `track by` clause, because then `ngOptions` will track the identity 30094 * of the item not by reference, but by the result of the `track by` expression. For example, if your 30095 * collection items have an id property, you would `track by item.id`. 30096 * 30097 * A different issue with objects or collections is that ngModel won't detect if an object property or 30098 * a collection item changes. For that reason, `ngOptions` additionally watches the model using 30099 * `$watchCollection`, when the expression contains a `track by` clause or the the select has the `multiple` attribute. 30100 * This allows ngOptions to trigger a re-rendering of the options even if the actual object/collection 30101 * has not changed identity, but only a property on the object or an item in the collection changes. 30102 * 30103 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 30104 * if the model is an array). This means that changing a property deeper than the first level inside the 30105 * object/collection will not trigger a re-rendering. 30106 * 30107 * ## `select` **`as`** 30108 * 30109 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 30110 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 30111 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 30112 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 30113 * 30114 * 30115 * ### `select` **`as`** and **`track by`** 30116 * 30117 * <div class="alert alert-warning"> 30118 * Be careful when using `select` **`as`** and **`track by`** in the same expression. 30119 * </div> 30120 * 30121 * Given this array of items on the $scope: 30122 * 30123 * ```js 30124 * $scope.items = [{ 30125 * id: 1, 30126 * label: 'aLabel', 30127 * subItem: { name: 'aSubItem' } 30128 * }, { 30129 * id: 2, 30130 * label: 'bLabel', 30131 * subItem: { name: 'bSubItem' } 30132 * }]; 30133 * ``` 30134 * 30135 * This will work: 30136 * 30137 * ```html 30138 * <select ng-options="item as item.label for item in items track by item.id" ng-model="selected"></select> 30139 * ``` 30140 * ```js 30141 * $scope.selected = $scope.items[0]; 30142 * ``` 30143 * 30144 * but this will not work: 30145 * 30146 * ```html 30147 * <select ng-options="item.subItem as item.label for item in items track by item.id" ng-model="selected"></select> 30148 * ``` 30149 * ```js 30150 * $scope.selected = $scope.items[0].subItem; 30151 * ``` 30152 * 30153 * In both examples, the **`track by`** expression is applied successfully to each `item` in the 30154 * `items` array. Because the selected option has been set programmatically in the controller, the 30155 * **`track by`** expression is also applied to the `ngModel` value. In the first example, the 30156 * `ngModel` value is `items[0]` and the **`track by`** expression evaluates to `items[0].id` with 30157 * no issue. In the second example, the `ngModel` value is `items[0].subItem` and the **`track by`** 30158 * expression evaluates to `items[0].subItem.id` (which is undefined). As a result, the model value 30159 * is not matched against any `<option>` and the `<select>` appears as having no selected value. 30160 * 30161 * 30162 * @param {string} ngModel Assignable angular expression to data-bind to. 30163 * @param {string=} name Property name of the form under which the control is published. 30164 * @param {string=} required The control is considered valid only if value is entered. 30165 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 30166 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 30167 * `required` when you want to data-bind to the `required` attribute. 30168 * @param {comprehension_expression=} ngOptions in one of the following forms: 30169 * 30170 * * for array data sources: 30171 * * `label` **`for`** `value` **`in`** `array` 30172 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 30173 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 30174 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 30175 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 30176 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 30177 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 30178 * (for including a filter with `track by`) 30179 * * for object data sources:
30180 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 30181 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 30182 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 30183 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 30184 * * `select` **`as`** `label` **`group by`** `group` 30185 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 30186 * * `select` **`as`** `label` **`disable when`** `disable` 30187 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 30188 * 30189 * Where: 30190 * 30191 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 30192 * * `value`: local variable which will refer to each item in the `array` or each property value 30193 * of `object` during iteration. 30194 * * `key`: local variable which will refer to a property name in `object` during iteration. 30195 * * `label`: The result of this expression will be the label for `<option>` element. The 30196 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 30197 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 30198 * element. If not specified, `select` expression will default to `value`. 30199 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 30200 * DOM element. 30201 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 30202 * element. Return `true` to disable. 30203 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 30204 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 30205 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 30206 * even when the options are recreated (e.g. reloaded from the server). 30207 * 30208 * @example 30209 <example module="selectExample"> 30210 <file name="index.html"> 30211 <script> 30212 angular.module('selectExample', []) 30213 .controller('ExampleController', ['$scope', function($scope) { 30214 $scope.colors = [ 30215 {name:'black', shade:'dark'}, 30216 {name:'white', shade:'light', notAnOption: true}, 30217 {name:'red', shade:'dark'}, 30218 {name:'blue', shade:'dark', notAnOption: true}, 30219 {name:'yellow', shade:'light', notAnOption: false} 30220 ]; 30221 $scope.myColor = $scope.colors[2]; // red 30222 }]); 30223 </script>
30224 <div ng-controller="ExampleController"> 30225 <ul> 30226 <li ng-repeat="color in colors"> 30227 <label>Name: <input ng-model="color.name"></label> 30228 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 30229 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 30230 </li> 30231 <li> 30232 <button ng-click="colors.push({})">add</button> 30233 </li> 30234 </ul> 30235 <hr/> 30236 <label>Color (null not allowed): 30237 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 30238 </label><br/> 30239 <label>Color (null allowed): 30240 <span class="nullable"> 30241 <select ng-model="myColor" ng-options="color.name for color in colors"> 30242 <option value="">-- choose color --</option> 30243 </select> 30244 </span></label><br/> 30245 30246 <label>Color grouped by shade: 30247 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 30248 </select> 30249 </label><br/> 30250 30251 <label>Color grouped by shade, with some disabled: 30252 <select ng-model="myColor" 30253 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 30254 </select> 30255 </label><br/> 30256 30257 30258 30259 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 30260 <br/> 30261 <hr/> 30262 Currently selected: {{ {selected_color:myColor} }} 30263 <div style="border:solid 1px black; height:20px" 30264 ng-style="{'background-color':myColor.name}"> 30265 </div> 30266 </div> 30267 </file> 30268 <file name="protractor.js" type="protractor"> 30269 it('should check ng-options', function() { 30270 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 30271 element.all(by.model('myColor')).first().click(); 30272 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 30273 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 30274 element(by.css('.nullable select[ng-model="myColor"]')).click(); 30275 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 30276 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 30277 }); 30278 </file> 30279 </example> 30280 */ 30281 30282// jshint maxlen: false 30283// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 30284var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/; 30285 // 1: value expression (valueFn) 30286 // 2: label expression (displayFn) 30287 // 3: group by expression (groupByFn) 30288 // 4: disable when expression (disableWhenFn) 30289 // 5: array item variable name 30290 // 6: object item key variable name 30291 // 7: object item value variable name 30292 // 8: collection expression 30293 // 9: track by expression 30294// jshint maxlen: 100 30295 30296 30297var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile, $document, $parse) { 30298 30299 function parseOptionsExpression(optionsExp, selectElement, scope) { 30300 30301 var match = optionsExp.match(NG_OPTIONS_REGEXP); 30302 if (!(match)) { 30303 throw ngOptionsMinErr('iexp', 30304 "Expected expression in form of " + 30305 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 30306 " but got '{0}'. Element: {1}", 30307 optionsExp, startingTag(selectElement)); 30308 } 30309 30310 // Extract the parts from the ngOptions expression 30311 30312 // The variable name for the value of the item in the collection 30313 var valueName = match[5] || match[7]; 30314 // The variable name for the key of the item in the collection 30315 var keyName = match[6]; 30316 30317 // An expression that generates the viewValue for an option if there is a label expression 30318 var selectAs = / as /.test(match[0]) && match[1]; 30319 // An expression that is used to track the id of each object in the options collection 30320 var trackBy = match[9]; 30321 // An expression that generates the viewValue for an option if there is no label expression 30322 var valueFn = $parse(match[2] ? match[1] : valueName); 30323 var selectAsFn = selectAs && $parse(selectAs); 30324 var viewValueFn = selectAsFn || valueFn; 30325 var trackByFn = trackBy && $parse(trackBy); 30326 30327 // Get the value by which we are going to track the option 30328 // if we have a trackFn then use that (passing scope and locals) 30329 // otherwise just hash the given viewValue 30330 var getTrackByValueFn = trackBy ? 30331 function(value, locals) { return trackByFn(scope, locals); } : 30332 function getHashOfValue(value) { return hashKey(value); }; 30333 var getTrackByValue = function(value, key) {
vendor: 5,803 bytes, lines 30334-30490
30334 return getTrackByValueFn(value, getLocals(value, key)); 30335 }; 30336 30337 var displayFn = $parse(match[2] || match[1]); 30338 var groupByFn = $parse(match[3] || ''); 30339 var disableWhenFn = $parse(match[4] || ''); 30340 var valuesFn = $parse(match[8]); 30341 30342 var locals = {}; 30343 var getLocals = keyName ? function(value, key) { 30344 locals[keyName] = key; 30345 locals[valueName] = value; 30346 return locals; 30347 } : function(value) { 30348 locals[valueName] = value; 30349 return locals; 30350 }; 30351 30352 30353 function Option(selectValue, viewValue, label, group, disabled) { 30354 this.selectValue = selectValue; 30355 this.viewValue = viewValue; 30356 this.label = label; 30357 this.group = group; 30358 this.disabled = disabled; 30359 } 30360 30361 function getOptionValuesKeys(optionValues) { 30362 var optionValuesKeys; 30363 30364 if (!keyName && isArrayLike(optionValues)) { 30365 optionValuesKeys = optionValues; 30366 } else { 30367 // if object, extract keys, in enumeration order, unsorted 30368 optionValuesKeys = []; 30369 for (var itemKey in optionValues) { 30370 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 30371 optionValuesKeys.push(itemKey); 30372 } 30373 } 30374 } 30375 return optionValuesKeys; 30376 } 30377 30378 return { 30379 trackBy: trackBy, 30380 getTrackByValue: getTrackByValue, 30381 getWatchables: $parse(valuesFn, function(optionValues) { 30382 // Create a collection of things that we would like to watch (watchedArray) 30383 // so that they can all be watched using a single $watchCollection 30384 // that only runs the handler once if anything changes 30385 var watchedArray = []; 30386 optionValues = optionValues || []; 30387 30388 var optionValuesKeys = getOptionValuesKeys(optionValues); 30389 var optionValuesLength = optionValuesKeys.length; 30390 for (var index = 0; index < optionValuesLength; index++) { 30391 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 30392 var value = optionValues[key]; 30393 30394 var locals = getLocals(value, key); 30395 var selectValue = getTrackByValueFn(value, locals); 30396 watchedArray.push(selectValue); 30397 30398 // Only need to watch the displayFn if there is a specific label expression 30399 if (match[2] || match[1]) { 30400 var label = displayFn(scope, locals); 30401 watchedArray.push(label); 30402 } 30403 30404 // Only need to watch the disableWhenFn if there is a specific disable expression 30405 if (match[4]) { 30406 var disableWhen = disableWhenFn(scope, locals); 30407 watchedArray.push(disableWhen); 30408 } 30409 } 30410 return watchedArray; 30411 }), 30412 30413 getOptions: function() { 30414 30415 var optionItems = []; 30416 var selectValueMap = {}; 30417 30418 // The option values were already computed in the `getWatchables` fn, 30419 // which must have been called to trigger `getOptions` 30420 var optionValues = valuesFn(scope) || []; 30421 var optionValuesKeys = getOptionValuesKeys(optionValues); 30422 var optionValuesLength = optionValuesKeys.length; 30423 30424 for (var index = 0; index < optionValuesLength; index++) { 30425 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 30426 var value = optionValues[key]; 30427 var locals = getLocals(value, key); 30428 var viewValue = viewValueFn(scope, locals); 30429 var selectValue = getTrackByValueFn(viewValue, locals); 30430 var label = displayFn(scope, locals); 30431 var group = groupByFn(scope, locals); 30432 var disabled = disableWhenFn(scope, locals); 30433 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 30434 30435 optionItems.push(optionItem); 30436 selectValueMap[selectValue] = optionItem; 30437 } 30438 30439 return { 30440 items: optionItems, 30441 selectValueMap: selectValueMap, 30442 getOptionFromViewValue: function(value) { 30443 return selectValueMap[getTrackByValue(value)]; 30444 }, 30445 getViewValueFromOption: function(option) { 30446 // If the viewValue could be an object that may be mutated by the application, 30447 // we need to make a copy and not return the reference to the value on the option. 30448 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 30449 } 30450 }; 30451 } 30452 }; 30453 } 30454 30455 30456 // we can't just jqLite('<option>') since jqLite is not smart enough 30457 // to create it in <select> and IE barfs otherwise. 30458 var optionTemplate = window.document.createElement('option'), 30459 optGroupTemplate = window.document.createElement('optgroup'); 30460 30461 function ngOptionsPostLink(scope, selectElement, attr, ctrls) { 30462 30463 var selectCtrl = ctrls[0]; 30464 var ngModelCtrl = ctrls[1]; 30465 var multiple = attr.multiple; 30466 30467 // The emptyOption allows the application developer to provide their own custom "empty" 30468 // option when the viewValue does not match any of the option values. 30469 var emptyOption; 30470 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 30471 if (children[i].value === '') { 30472 emptyOption = children.eq(i); 30473 break; 30474 } 30475 } 30476 30477 var providedEmptyOption = !!emptyOption; 30478 30479 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 30480 unknownOption.val('?'); 30481 30482 var options; 30483 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 30484 // This stores the newly created options before they are appended to the select. 30485 // Since the contents are removed from the fragment when it is appended, 30486 // we only need to create it once. 30487 var listFragment = $document[0].createDocumentFragment(); 30488 30489 var renderEmptyOption = function() { 30490 if (!providedEmptyOption) {
30491 selectElement.prepend(emptyOption); 30492 } 30493 selectElement.val(''); 30494 emptyOption.prop('selected', true); // needed for IE 30495 emptyOption.attr('selected', true); 30496 }; 30497 30498 var removeEmptyOption = function() { 30499 if (!providedEmptyOption) { 30500 emptyOption.remove(); 30501 } 30502 }; 30503 30504 30505 var renderUnknownOption = function() { 30506 selectElement.prepend(unknownOption); 30507 selectElement.val('?'); 30508 unknownOption.prop('selected', true); // needed for IE 30509 unknownOption.attr('selected', true); 30510 }; 30511 30512 var removeUnknownOption = function() { 30513 unknownOption.remove(); 30514 }; 30515 30516 // Update the controller methods for multiple selectable options 30517 if (!multiple) { 30518 30519 selectCtrl.writeValue = function writeNgOptionsValue(value) { 30520 var option = options.getOptionFromViewValue(value); 30521 30522 if (option) { 30523 // Don't update the option when it is already selected. 30524 // For example, the browser will select the first option by default. In that case, 30525 // most properties are set automatically - except the `selected` attribute, which we 30526 // set always 30527 30528 if (selectElement[0].value !== option.selectValue) { 30529 removeUnknownOption(); 30530 removeEmptyOption(); 30531 30532 selectElement[0].value = option.selectValue; 30533 option.element.selected = true; 30534 } 30535 30536 option.element.setAttribute('selected', 'selected'); 30537 } else { 30538 if (value === null || providedEmptyOption) { 30539 removeUnknownOption(); 30540 renderEmptyOption(); 30541 } else { 30542 removeEmptyOption(); 30543 renderUnknownOption(); 30544 } 30545 } 30546 }; 30547 30548 selectCtrl.readValue = function readNgOptionsValue() { 30549 30550 var selectedOption = options.selectValueMap[selectElement.val()]; 30551 30552 if (selectedOption && !selectedOption.disabled) { 30553 removeEmptyOption(); 30554 removeUnknownOption(); 30555 return options.getViewValueFromOption(selectedOption); 30556 } 30557 return null; 30558 }; 30559 30560 // If we are using `track by` then we must watch the tracked value on the model 30561 // since ngModel only watches for object identity change 30562 if (ngOptions.trackBy) { 30563 scope.$watch( 30564 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 30565 function() { ngModelCtrl.$render(); } 30566 ); 30567 } 30568 30569 } else { 30570 30571 ngModelCtrl.$isEmpty = function(value) { 30572 return !value || value.length === 0; 30573 }; 30574 30575 30576 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 30577 options.items.forEach(function(option) { 30578 option.element.selected = false; 30579 }); 30580 30581 if (value) { 30582 value.forEach(function(item) { 30583 var option = options.getOptionFromViewValue(item); 30584 if (option) option.element.selected = true; 30585 }); 30586 } 30587 }; 30588 30589 30590 selectCtrl.readValue = function readNgOptionsMultiple() { 30591 var selectedValues = selectElement.val() || [], 30592 selections = []; 30593 30594 forEach(selectedValues, function(value) { 30595 var option = options.selectValueMap[value]; 30596 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 30597 }); 30598 30599 return selections; 30600 }; 30601 30602 // If we are using `track by` then we must watch these tracked values on the model 30603 // since ngModel only watches for object identity change 30604 if (ngOptions.trackBy) { 30605 30606 scope.$watchCollection(function() { 30607 if (isArray(ngModelCtrl.$viewValue)) { 30608 return ngModelCtrl.$viewValue.map(function(value) { 30609 return ngOptions.getTrackByValue(value); 30610 }); 30611 } 30612 }, function() { 30613 ngModelCtrl.$render(); 30614 }); 30615 30616 } 30617 } 30618 30619 30620 if (providedEmptyOption) { 30621 30622 // we need to remove it before calling selectElement.empty() because otherwise IE will 30623 // remove the label from the element. wtf? 30624 emptyOption.remove(); 30625 30626 // compile the element since there might be bindings in it 30627 $compile(emptyOption)(scope); 30628 30629 // remove the class, which is added automatically because we recompile the element and it 30630 // becomes the compilation root 30631 emptyOption.removeClass('ng-scope'); 30632 } else { 30633 emptyOption = jqLite(optionTemplate.cloneNode(false)); 30634 } 30635 30636 selectElement.empty(); 30637 30638 // We need to do this here to ensure that the options object is defined 30639 // when we first hit it in writeNgOptionsValue 30640 updateOptions(); 30641 30642 // We will re-render the option elements if the option values or labels change 30643 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 30644 30645 // ------------------------------------------------------------------ // 30646 30647 function addOptionElement(option, parent) { 30648 var optionElement = optionTemplate.cloneNode(false); 30649 parent.appendChild(optionElement); 30650 updateOptionElement(option, optionElement); 30651 } 30652 30653 30654 function updateOptionElement(option, element) { 30655 option.element = element; 30656 element.disabled = option.disabled; 30657 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive 30658 // selects in certain circumstances when multiple selects are next to each other and display 30659 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 30660 // See https://github.com/angular/angular.js/issues/11314 for more info. 30661 // This is unfortunately untestable with unit / e2e tests 30662 if (option.label !== element.label) { 30663 element.label = option.label; 30664 element.textContent = option.label; 30665 } 30666 if (option.value !== element.value) element.value = option.selectValue; 30667 } 30668 30669 function updateOptions() { 30670 var previousValue = options && selectCtrl.readValue(); 30671 30672 // We must remove all current options, but cannot simply set innerHTML = null 30673 // since the providedEmptyOption might have an ngIf on it that inserts comments which we 30674 // must preserve. 30675 // Instead, iterate over the current option elements and remove them or their optgroup 30676 // parents 30677 if (options) { 30678 30679 for (var i = options.items.length - 1; i >= 0; i--) { 30680 var option = options.items[i]; 30681 if (option.group) { 30682 jqLiteRemove(option.element.parentNode); 30683 } else { 30684 jqLiteRemove(option.element); 30685 } 30686 } 30687 } 30688 30689 options = ngOptions.getOptions(); 30690 30691 var groupElementMap = {}; 30692 30693 // Ensure that the empty option is always there if it was explicitly provided 30694 if (providedEmptyOption) {
vendor: 4,500 bytes, lines 30695-30812
30695 selectElement.prepend(emptyOption); 30696 } 30697 30698 options.items.forEach(function addOption(option) { 30699 var groupElement; 30700 30701 if (isDefined(option.group)) { 30702 30703 // This option is to live in a group 30704 // See if we have already created this group 30705 groupElement = groupElementMap[option.group]; 30706 30707 if (!groupElement) { 30708 30709 groupElement = optGroupTemplate.cloneNode(false); 30710 listFragment.appendChild(groupElement); 30711 30712 // Update the label on the group element 30713 groupElement.label = option.group; 30714 30715 // Store it for use later 30716 groupElementMap[option.group] = groupElement; 30717 } 30718 30719 addOptionElement(option, groupElement); 30720 30721 } else { 30722 30723 // This option is not in a group 30724 addOptionElement(option, listFragment); 30725 } 30726 }); 30727 30728 selectElement[0].appendChild(listFragment); 30729 30730 ngModelCtrl.$render(); 30731 30732 // Check to see if the value has changed due to the update to the options 30733 if (!ngModelCtrl.$isEmpty(previousValue)) { 30734 var nextValue = selectCtrl.readValue(); 30735 var isNotPrimitive = ngOptions.trackBy || multiple; 30736 if (isNotPrimitive ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 30737 ngModelCtrl.$setViewValue(nextValue); 30738 ngModelCtrl.$render(); 30739 } 30740 } 30741 30742 } 30743 } 30744 30745 return { 30746 restrict: 'A', 30747 terminal: true, 30748 require: ['select', 'ngModel'], 30749 link: { 30750 pre: function ngOptionsPreLink(scope, selectElement, attr, ctrls) { 30751 // Deactivate the SelectController.register method to prevent 30752 // option directives from accidentally registering themselves 30753 // (and unwanted $destroy handlers etc.) 30754 ctrls[0].registerOption = noop; 30755 }, 30756 post: ngOptionsPostLink 30757 } 30758 }; 30759}]; 30760 30761/** 30762 * @ngdoc directive 30763 * @name ngPluralize 30764 * @restrict EA 30765 * 30766 * @description 30767 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 30768 * These rules are bundled with angular.js, but can be overridden 30769 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 30770 * by specifying the mappings between 30771 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 30772 * and the strings to be displayed. 30773 * 30774 * # Plural categories and explicit number rules 30775 * There are two 30776 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 30777 * in Angular's default en-US locale: "one" and "other". 30778 * 30779 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 30780 * any number that is not 1), an explicit number rule can only match one number. For example, the 30781 * explicit number rule for "3" matches the number 3. There are examples of plural categories 30782 * and explicit number rules throughout the rest of this documentation. 30783 * 30784 * # Configuring ngPluralize 30785 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 30786 * You can also provide an optional attribute, `offset`. 30787 * 30788 * The value of the `count` attribute can be either a string or an {@link guide/expression 30789 * Angular expression}; these are evaluated on the current scope for its bound value. 30790 * 30791 * The `when` attribute specifies the mappings between plural categories and the actual 30792 * string to be displayed. The value of the attribute should be a JSON object. 30793 * 30794 * The following example shows how to configure ngPluralize: 30795 * 30796 * ```html 30797 * <ng-pluralize count="personCount" 30798 when="{'0': 'Nobody is viewing.', 30799 * 'one': '1 person is viewing.', 30800 * 'other': '{} people are viewing.'}"> 30801 * </ng-pluralize> 30802 *``` 30803 * 30804 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 30805 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 30806 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 30807 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 30808 * show "a dozen people are viewing". 30809 * 30810 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 30811 * into pluralized strings. In the previous example, Angular will replace `{}` with 30812 * <span ng-non-bindable>`{{personCount}}
30812`</span>. The closed braces `{}` is a placeholder 30813 * for <span ng-non-bindable>{{numberExpression}}</span>. 30814 * 30815 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 30816 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 30817 * 30818 * # Configuring ngPluralize with offset 30819 * The `offset` attribute allows further customization of pluralized text, which can result in 30820 * a better user experience. For example, instead of the message "4 people are viewing this document", 30821 * you might display "John, Kate and 2 others are viewing this document". 30822 * The offset attribute allows you to offset a number by any desired value. 30823 * Let's take a look at an example: 30824 * 30825 * ```html 30826 * <ng-pluralize count="personCount" offset=2 30827 * when="{'0': 'Nobody is viewing.', 30828 * '1': '{{person1}} is viewing.', 30829 * '2': '{{person1}} and {{person2}} are viewing.', 30830 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 30831 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 30832 * </ng-pluralize> 30833 * ``` 30834 * 30835 * Notice that we are still using two plural categories(one, other), but we added 30836 * three explicit number rules 0, 1 and 2. 30837 * When one person, perhaps John, views the document, "John is viewing" will be shown. 30838 * When three people view the document, no explicit number rule is found, so 30839 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 30840 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 30841 * is shown. 30842 * 30843 * Note that when you specify offsets, you must provide explicit number rules for 30844 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 30845 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 30846 * plural categories "one" and "other". 30847 * 30848 * @param {string|expression} count The variable to be bound to. 30849 * @param {string} when The mapping between plural category to its corresponding strings. 30850 * @param {number=} offset Offset to deduct from the total number. 30851 * 30852 * @example 30853 <example module="pluralizeExampl
vendor: 4,913 bytes, lines 30853-30967
30853e"> 30854 <file name="index.html"> 30855 <script> 30856 angular.module('pluralizeExample', []) 30857 .controller('ExampleController', ['$scope', function($scope) { 30858 $scope.person1 = 'Igor'; 30859 $scope.person2 = 'Misko'; 30860 $scope.personCount = 1; 30861 }]); 30862 </script> 30863 <div ng-controller="ExampleController"> 30864 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 30865 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 30866 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 30867 30868 <!--- Example with simple pluralization rules for en locale ---> 30869 Without Offset: 30870 <ng-pluralize count="personCount" 30871 when="{'0': 'Nobody is viewing.', 30872 'one': '1 person is viewing.', 30873 'other': '{} people are viewing.'}"> 30874 </ng-pluralize><br> 30875 30876 <!--- Example with offset ---> 30877 With Offset(2): 30878 <ng-pluralize count="personCount" offset=2 30879 when="{'0': 'Nobody is viewing.', 30880 '1': '{{person1}} is viewing.', 30881 '2': '{{person1}} and {{person2}} are viewing.', 30882 'one': '{{person1}}, {{person2}} and one other person are viewing.', 30883 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 30884 </ng-pluralize> 30885 </div> 30886 </file> 30887 <file name="protractor.js" type="protractor"> 30888 it('should show correct pluralized string', function() { 30889 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 30890 var withOffset = element.all(by.css('ng-pluralize')).get(1); 30891 var countInput = element(by.model('personCount')); 30892 30893 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 30894 expect(withOffset.getText()).toEqual('Igor is viewing.'); 30895 30896 countInput.clear(); 30897 countInput.sendKeys('0'); 30898 30899 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 30900 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 30901 30902 countInput.clear(); 30903 countInput.sendKeys('2'); 30904 30905 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 30906 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 30907 30908 countInput.clear(); 30909 countInput.sendKeys('3'); 30910 30911 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 30912 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 30913 30914 countInput.clear(); 30915 countInput.sendKeys('4'); 30916 30917 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 30918 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 30919 }); 30920 it('should show data-bound names', function() { 30921 var withOffset = element.all(by.css('ng-pluralize')).get(1); 30922 var personCount = element(by.model('personCount')); 30923 var person1 = element(by.model('person1')); 30924 var person2 = element(by.model('person2')); 30925 personCount.clear(); 30926 personCount.sendKeys('4'); 30927 person1.clear(); 30928 person1.sendKeys('Di'); 30929 person2.clear(); 30930 person2.sendKeys('Vojta'); 30931 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 30932 }); 30933 </file> 30934 </example> 30935 */ 30936var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 30937 var BRACE = /{}/g, 30938 IS_WHEN = /^when(Minus)?(.+)$/; 30939 30940 return { 30941 link: function(scope, element, attr) { 30942 var numberExp = attr.count, 30943 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 30944 offset = attr.offset || 0, 30945 whens = scope.$eval(whenExp) || {}, 30946 whensExpFns = {}, 30947 startSymbol = $interpolate.startSymbol(), 30948 endSymbol = $interpolate.endSymbol(), 30949 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 30950 watchRemover = angular.noop, 30951 lastCount; 30952 30953 forEach(attr, function(expression, attributeName) { 30954 var tmpMatch = IS_WHEN.exec(attributeName); 30955 if (tmpMatch) { 30956 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 30957 whens[whenKey] = element.attr(attr.$attr[attributeName]); 30958 } 30959 }); 30960 forEach(whens, function(expression, key) { 30961 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 30962 30963 }); 30964 30965 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 30966 var count = parseFloat(newVal); 30967 var countIsNaN = isN
vendor: 9,019 bytes, lines 30967-31175
30967aN(count); 30968 30969 if (!countIsNaN && !(count in whens)) { 30970 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 30971 // Otherwise, check it against pluralization rules in $locale service. 30972 count = $locale.pluralCat(count - offset); 30973 } 30974 30975 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 30976 // In JS `NaN !== NaN`, so we have to explicitly check. 30977 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 30978 watchRemover(); 30979 var whenExpFn = whensExpFns[count]; 30980 if (isUndefined(whenExpFn)) { 30981 if (newVal != null) { 30982 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 30983 } 30984 watchRemover = noop; 30985 updateElementText(); 30986 } else { 30987 watchRemover = scope.$watch(whenExpFn, updateElementText); 30988 } 30989 lastCount = count; 30990 } 30991 }); 30992 30993 function updateElementText(newText) { 30994 element.text(newText || ''); 30995 } 30996 } 30997 }; 30998}]; 30999 31000/** 31001 * @ngdoc directive 31002 * @name ngRepeat 31003 * @multiElement 31004 * 31005 * @description 31006 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 31007 * instance gets its own scope, where the given loop variable is set to the current collection item, 31008 * and `$index` is set to the item index or key. 31009 * 31010 * Special properties are exposed on the local scope of each template instance, including: 31011 * 31012 * | Variable | Type | Details | 31013 * |-----------|-----------------|-----------------------------------------------------------------------------| 31014 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 31015 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 31016 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 31017 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 31018 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 31019 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 31020 * 31021 * <div class="alert alert-info"> 31022 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 31023 * This may be useful when, for instance, nesting ngRepeats. 31024 * </div> 31025 * 31026 * 31027 * # Iterating over object properties 31028 * 31029 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 31030 * syntax: 31031 * 31032 * ```js 31033 * <div ng-repeat="(key, value) in myObj"> ... </div> 31034 * ``` 31035 * 31036 * However, there are a limitations compared to array iteration: 31037 * 31038 * - The JavaScript specification does not define the order of keys 31039 * returned for an object, so Angular relies on the order returned by the browser 31040 * when running `for key in myObj`. Browsers generally follow the strategy of providing 31041 * keys in the order in which they were defined, although there are exceptions when keys are deleted 31042 * and reinstated. See the 31043 * [MDN page on `delete` for more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_notes). 31044 * 31045 * - `ngRepeat` will silently *ignore* object keys starting with `$`, because 31046 * it's a prefix used by Angular for public (`$`) and private (`$$`) properties. 31047 * 31048 * - The built-in filters {@link ng.orderBy orderBy} and {@link ng.filter filter} do not work with 31049 * objects, and will throw if used with one. 31050 * 31051 * If you are hitting any of these limitations, the recommended workaround is to convert your object into an array 31052 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 31053 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 31054 * or implement a `$watch` on the object yourself. 31055 * 31056 * 31057 * # Tracking and Duplicates 31058 * 31059 * `ngRepeat` uses {@link $rootScope.Scope#$watchCollection $watchCollection} to detect changes in 31060 * the collection. When a change happens, ngRepeat then makes the corresponding changes to the DOM: 31061 * 31062 * * When an item is added, a new instance of the template is added to the DOM. 31063 * * When an item is removed, its template instance is removed from the DOM. 31064 * * When items are reordered, their respective templates are reordered in the DOM. 31065 * 31066 * To minimize creation of DOM elements, `ngRepeat` uses a function 31067 * to "keep track" of all items in the collection and their corresponding DOM elements. 31068 * For example, if an item is added to the collection, ngRepeat will know that all other items 31069 * already have DOM elements, and will not re-render them. 31070 * 31071 * The default tracking function (which tracks items by their identity) does not allow 31072 * duplicate items in arrays. This is because when there are duplicates, it is not possible 31073 * to maintain a one-to-one mapping between collection items and DOM elements. 31074 * 31075 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 31076 * with your own using the `track by` expression. 31077 * 31078 * For example, you may track items by the index of each item in the collection, using the 31079 * special scope property `$index`: 31080 * ```html 31081 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 31082 * {{n}} 31083 * </div> 31084 * ``` 31085 * 31086 * You may also use arbitrary expressions in `track by`, including references to custom functions 31087 * on the scope: 31088 * ```html 31089 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 31090 * {{n}} 31091 * </div> 31092 * ``` 31093 * 31094 * <div class="alert alert-success"> 31095 * If you are working with objects that have an identifier property, you should track 31096 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 31097 * will not have to rebuild the DOM elements for items it has already rendered, even if the 31098 * JavaScript objects in the collection have been substituted for new ones. For large collections, 31099 * this significantly improves rendering performance. If you don't have a unique identifier, 31100 * `track by $index` can also provide a performance boost. 31101 * </div> 31102 * ```html 31103 * <div ng-repeat="model in collection track by model.id"> 31104 * {{model.name}} 31105 * </div> 31106 * ``` 31107 * 31108 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 31109 * `$id` function, which tracks items by their identity: 31110 * ```html 31111 * <div ng-repeat="obj in collection track by $id(obj)"> 31112 * {{obj.prop}} 31113 * </div> 31114 * ``` 31115 * 31116 * <div class="alert alert-warning"> 31117 * **Note:** `track by` must always be the last expression: 31118 * </div> 31119 * ``` 31120 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 31121 * {{model.name}} 31122 * </div> 31123 * ``` 31124 * 31125 * # Special repeat start and end points 31126 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 31127 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 31128 * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) 31129 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 31130 * 31131 * The example below makes use of this feature: 31132 * ```html 31133 * <header ng-repeat-start="item in items"> 31134 * Header {{ item }} 31135 * </header> 31136 * <div class="body"> 31137 * Body {{ item }} 31138 * </div> 31139 * <footer ng-repeat-end> 31140 * Footer {{ item }} 31141 * </footer> 31142 * ``` 31143 * 31144 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 31145 * ```html 31146 * <header> 31147 * Header A 31148 * </header> 31149 * <div class="body"> 31150 * Body A 31151 * </div> 31152 * <footer> 31153 * Footer A 31154 * </footer> 31155 * <header> 31156 * Header B 31157 * </header> 31158 * <div class="body"> 31159 * Body B 31160 * </div> 31161 * <footer> 31162 * Footer B 31163 * </footer> 31164 * ``` 31165 * 31166 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 31167 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 31168 * 31169 * @animations 31170 * | Animation | Occurs | 31171 * |----------------------------------|-------------------------------------| 31172 * | {@link ng.$animate#enter enter} | when a new item is added to the list or when an item is revealed after a filter | 31173 * | {@link ng.$animate#leave leave} | when an item is removed from the list or when an item is filtered out | 31174 * | {@link ng.$animate#move move } | when an adjacent item is filtered out causing a reorder or when the item contents are reordered | 31175 *
31176 * See the example below for defining CSS animations with ngRepeat. 31177 * 31178 * @element ANY 31179 * @scope 31180 * @priority 1000 31181 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 31182 * formats are currently supported: 31183 * 31184 * * `variable in expression` â where variable is the user defined loop variable and `expression` 31185 * is a scope expression giving the collection to enumerate. 31186 * 31187 * For example: `album in artist.albums`. 31188 * 31189 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 31190 * and `expression` is the scope expression giving the collection to enumerate. 31191 * 31192 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 31193 * 31194 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 31195 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 31196 * is specified, ng-repeat associates elements by identity. It is an error to have 31197 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 31198 * mapped to the same DOM element, which is not possible.) 31199 * 31200 * Note that the tracking expression must come last, after any filters, and the alias expression. 31201 * 31202 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 31203 * will be associated by item identity in the array. 31204 * 31205 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 31206 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 31207 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 31208 * element in the same way in the DOM. 31209 * 31210 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 31211 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 31212 * property is same. 31213 * 31214 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 31215 * to items in conjunction with a tracking expression. 31216 * 31217 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 31218 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 31219 * when a filter is active on the repeater, but the filtered result set is empty. 31220 * 31221 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 31222 * the items have been processed through the filter. 31223 * 31224 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end 31225 * (and not as operator, inside an expression). 31226 * 31227 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 31228 * 31229 * @example 31230 * This example uses `ngRepeat` to display a list of people. A filter is used to restrict the displayed 31231 * results by name. New (entering) and removed (leaving) items are animated. 31232 <example module="ngRepeat" name="ngRepeat" deps="angular-animate.js" animations="true"> 31233 <file name="index.html"> 31234 <div ng-controller="repeatController"> 31235 I have {{friends.length}} friends. They are: 31236 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 31237 <ul class="example-animate-container"> 31238 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 31239 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 31240 </li> 31241 <li class="animate-repeat" ng-if="results.length == 0"> 31242 <strong>No results found...</strong> 31243 </li> 31244 </ul> 31245 </div> 31246 </file> 31247 <file name="script.js"> 31248 angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope) { 31249 $scope.friends = [ 31250 {name:'John', age:25, gender:'boy'}, 31251 {name:'Jessie', age:30, gender:'girl'}, 31252 {name:'Johanna', age:28, gender:'girl'}, 31253 {name:'Joy', age:15, gender:'girl'}, 31254 {name:'Mary', age:28, gender:'girl'}, 31255 {name:'Peter', age:95, gender:'boy'}, 31256 {name:'Sebastian', age:50, gender:'boy'}, 31257 {name:'Erika', age:27, gender:'girl'}, 31258 {name:'Patrick', age:40, gender:'boy'}, 31259 {name:'Samantha', age:60, gender:'girl'} 31260 ]; 31261 }); 31262 </file> 31263 <file name="animations.css"> 31264 .example-animate-container { 31265 background:white; 31266 border:1px solid black; 31267 list-style:none; 31268 margin:0; 31269 padding:0 10px; 31270 } 31271 31272 .animate-repeat { 31273 line-height:30px; 31274 list-style:none; 31275 box-sizing:border-box; 31276 } 31277 31278 .animate-repeat.ng-move, 31279 .animate-repeat.ng-enter, 31280 .animate-repeat.ng-leave { 31281 transition:all linear 0.5s; 31282 } 31283 31284 .animate-repeat.ng-leave.ng-leave-active, 31285 .animate-repeat.ng-move, 31286 .animate-repeat.ng-enter { 31287 opacity:0; 31288 max-height:0; 31289 } 31290 31291 .animate-repeat.ng-leave,
31292 .animate-repeat.ng-move.ng-move-active, 31293 .animate-repeat.ng-enter.ng-enter-active { 31294 opacity:1; 31295 max-height:30px; 31296 } 31297 </file> 31298 <file name="protractor.js" type="protractor"> 31299 var friends = element.all(by.repeater('friend in friends')); 31300 31301 it('should render initial data set', function() { 31302 expect(friends.count()).toBe(10); 31303 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 31304 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 31305 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 31306 expect(element(by.binding('friends.length')).getText()) 31307 .toMatch("I have 10 friends. They are:"); 31308 }); 31309 31310 it('should update repeater when filter predicate changes', function() { 31311 expect(friends.count()).toBe(10); 31312 31313 element(by.model('q')).sendKeys('ma'); 31314 31315 expect(friends.count()).toBe(2); 31316 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 31317 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 31318 }); 31319 </file> 31320 </example> 31321 */ 31322var ngRepeatDirective = ['$parse', '$animate', '$compile', function($parse, $animate, $compile) { 31323 var NG_REMOVED = '$$NG_REMOVED'; 31324 var ngRepeatMinErr = minErr('ngRepeat'); 31325 31326 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 31327 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 31328 scope[valueIdentifier] = value; 31329 if (keyIdentifier) scope[keyIdentifier] = key; 31330 scope.$index = index; 31331 scope.$first = (index === 0); 31332 scope.$last = (index === (arrayLength - 1)); 31333 scope.$middle = !(scope.$first || scope.$last); 31334 // jshint bitwise: false 31335 scope.$odd = !(scope.$even = (index&1) === 0); 31336 // jshint bitwise: true 31337 }; 31338 31339 var getBlockStart = function(block) { 31340 return block.clone[0]; 31341 }; 31342 31343 var getBlockEnd = function(block) { 31344 return block.clone[block.clone.length - 1]; 31345 }; 31346 31347 31348 return { 31349 restrict: 'A', 31350 multiElement: true, 31351 transclude: 'element', 31352 priority: 1000, 31353 terminal: true, 31354 $$tlb: true, 31355 compile: function ngRepeatCompile($element, $attr) { 31356 var expression = $attr.ngRepeat; 31357 var ngRepeatEndComment = $compile.$$createComment('end ngRepeat', expression); 31358 31359 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); 31360 31361 if (!match) { 31362 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 31363 expression); 31364 } 31365 31366 var lhs = match[1]; 31367 var rhs = match[2]; 31368 var aliasAs = match[3]; 31369 var trackByExp = match[4]; 31370 31371 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 31372 31373 if (!match) { 31374 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 31375 lhs); 31376 } 31377 var valueIdentifier = match[3] || match[1]; 31378 var keyIdentifier = match[2]; 31379 31380 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 31381 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 31382 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.", 31383 aliasAs); 31384 } 31385 31386 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 31387 var hashFnLocals = {$id: hashKey}; 31388 31389 if (trackByExp) { 31390 trackByExpGetter = $parse(trackByExp); 31391 } else { 31392 trackByIdArrayFn = function(key, value) { 31393 return hashKey(value); 31394 }; 31395 trackByIdObjFn = function(key) { 31396 return key; 31397 }; 31398 } 31399 31400 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 31401 31402 if (trackByExpGetter) { 31403 trackByIdExpFn = function(key, value, index) {
vendor: 11,339 bytes, lines 31404-31655
31404 // assign key, value, and $index to the locals so that they can be used in hash functions 31405 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 31406 hashFnLocals[valueIdentifier] = value; 31407 hashFnLocals.$index = index; 31408 return trackByExpGetter($scope, hashFnLocals); 31409 }; 31410 } 31411 31412 // Store a list of elements from previous run. This is a hash where key is the item from the 31413 // iterator, and the value is objects with following properties. 31414 // - scope: bound scope 31415 // - element: previous element. 31416 // - index: position 31417 // 31418 // We are using no-proto object so that we don't need to guard against inherited props via 31419 // hasOwnProperty. 31420 var lastBlockMap = createMap(); 31421 31422 //watch props 31423 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 31424 var index, length, 31425 previousNode = $element[0], // node that cloned nodes should be inserted after 31426 // initialized to the comment node anchor 31427 nextNode, 31428 // Same as lastBlockMap but it has the current state. It will become the 31429 // lastBlockMap on the next iteration. 31430 nextBlockMap = createMap(), 31431 collectionLength, 31432 key, value, // key/value of iteration 31433 trackById, 31434 trackByIdFn, 31435 collectionKeys, 31436 block, // last object information {scope, element, id} 31437 nextBlockOrder, 31438 elementsToRemove; 31439 31440 if (aliasAs) { 31441 $scope[aliasAs] = collection; 31442 } 31443 31444 if (isArrayLike(collection)) { 31445 collectionKeys = collection; 31446 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 31447 } else { 31448 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 31449 // if object, extract keys, in enumeration order, unsorted 31450 collectionKeys = []; 31451 for (var itemKey in collection) { 31452 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 31453 collectionKeys.push(itemKey); 31454 } 31455 } 31456 } 31457 31458 collectionLength = collectionKeys.length; 31459 nextBlockOrder = new Array(collectionLength); 31460 31461 // locate existing items 31462 for (index = 0; index < collectionLength; index++) { 31463 key = (collection === collectionKeys) ? index : collectionKeys[index]; 31464 value = collection[key]; 31465 trackById = trackByIdFn(key, value, index); 31466 if (lastBlockMap[trackById]) { 31467 // found previously seen block 31468 block = lastBlockMap[trackById]; 31469 delete lastBlockMap[trackById]; 31470 nextBlockMap[trackById] = block; 31471 nextBlockOrder[index] = block; 31472 } else if (nextBlockMap[trackById]) { 31473 // if collision detected. restore lastBlockMap and throw an error 31474 forEach(nextBlockOrder, function(block) { 31475 if (block && block.scope) lastBlockMap[block.id] = block; 31476 }); 31477 throw ngRepeatMinErr('dupes', 31478 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 31479 expression, trackById, value); 31480 } else { 31481 // new never before seen block 31482 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 31483 nextBlockMap[trackById] = true; 31484 } 31485 } 31486 31487 // remove leftover items 31488 for (var blockKey in lastBlockMap) { 31489 block = lastBlockMap[blockKey]; 31490 elementsToRemove = getBlockNodes(block.clone); 31491 $animate.leave(elementsToRemove); 31492 if (elementsToRemove[0].parentNode) { 31493 // if the element was not removed yet because of pending animation, mark it as deleted 31494 // so that we can ignore it later 31495 for (index = 0, length = elementsToRemove.length; index < length; index++) { 31496 elementsToRemove[index][NG_REMOVED] = true; 31497 } 31498 } 31499 block.scope.$destroy(); 31500 } 31501 31502 // we are not using forEach for perf reasons (trying to avoid #call) 31503 for (index = 0; index < collectionLength; index++) { 31504 key = (collection === collectionKeys) ? index : collectionKeys[index]; 31505 value = collection[key]; 31506 block = nextBlockOrder[index]; 31507 31508 if (block.scope) { 31509 // if we have already seen this object, then we need to reuse the 31510 // associated scope/element 31511 31512 nextNode = previousNode; 31513 31514 // skip nodes that are already pending removal via leave animation 31515 do { 31516 nextNode = nextNode.nextSibling; 31517 } while (nextNode && nextNode[NG_REMOVED]); 31518 31519 if (getBlockStart(block) != nextNode) { 31520 // existing item which got moved 31521 $animate.move(getBlockNodes(block.clone), null, previousNode); 31522 } 31523 previousNode = getBlockEnd(block); 31524 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 31525 } else { 31526 // new item which we don't know about 31527 $transclude(function ngRepeatTransclude(clone, scope) { 31528 block.scope = scope; 31529 // http://jsperf.com/clone-vs-createcomment 31530 var endNode = ngRepeatEndComment.cloneNode(false); 31531 clone[clone.length++] = endNode; 31532 31533 $animate.enter(clone, null, previousNode); 31534 previousNode = endNode; 31535 // Note: We only need the first/last node of the cloned nodes. 31536 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 31537 // by a directive with templateUrl when its template arrives. 31538 block.clone = clone; 31539 nextBlockMap[block.id] = block; 31540 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 31541 }); 31542 } 31543 } 31544 lastBlockMap = nextBlockMap; 31545 }); 31546 }; 31547 } 31548 }; 31549}]; 31550 31551var NG_HIDE_CLASS = 'ng-hide'; 31552var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 31553/** 31554 * @ngdoc directive 31555 * @name ngShow 31556 * @multiElement 31557 * 31558 * @description 31559 * The `ngShow` directive shows or hides the given HTML element based on the expression 31560 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 31561 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 31562 * in AngularJS and sets the display style to none (using an !important flag). 31563 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 31564 * 31565 * ```html 31566 * <!-- when $scope.myValue is truthy (element is visible) --> 31567 * <div ng-show="myValue"></div> 31568 * 31569 * <!-- when $scope.myValue is falsy (element is hidden) --> 31570 * <div ng-show="myValue" class="ng-hide"></div> 31571 * ``` 31572 * 31573 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 31574 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 31575 * from the element causing the element not to appear hidden. 31576 * 31577 * ## Why is !important used? 31578 * 31579 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 31580 * can be easily overridden by heavier selectors. For example, something as simple 31581 * as changing the display style on a HTML list item would make hidden elements appear visible. 31582 * This also becomes a bigger issue when dealing with CSS frameworks. 31583 * 31584 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 31585 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 31586 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 31587 * 31588 * ### Overriding `.ng-hide` 31589 * 31590 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 31591 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 31592 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 31593 * with extra animation classes that can be added. 31594 * 31595 * ```css 31596 * .ng-hide:not(.ng-hide-animate) { 31597 * /* this is just another form of hiding an element */ 31598 * display: block!important; 31599 * position: absolute; 31600 * top: -9999px; 31601 * left: -9999px; 31602 * } 31603 * ``` 31604 * 31605 * By default you don't need to override in CSS anything and the animations will work around the display style. 31606 * 31607 * ## A note about animations with `ngShow` 31608 * 31609 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 31610 * is true and false. This system works like the animation system present with ngClass except that 31611 * you must also include the !important flag to override the display property 31612 * so that you can perform an animation when the element is hidden during the time of the animation. 31613 * 31614 * ```css 31615 * // 31616 * //a working example can be found at the bottom of this page 31617 * // 31618 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 31619 * /* this is required as of 1.3x to properly 31620 * apply all styling in a show/hide animation */ 31621 * transition: 0s linear all; 31622 * } 31623 * 31624 * .my-element.ng-hide-add-active, 31625 * .my-element.ng-hide-remove-active { 31626 * /* the transition is defined in the active class */ 31627 * transition: 1s linear all; 31628 * } 31629 * 31630 * .my-element.ng-hide-add { ... } 31631 * .my-element.ng-hide-add.ng-hide-add-active { ... } 31632 * .my-element.ng-hide-remove { ... } 31633 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 31634 * ``` 31635 * 31636 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display 31637 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 31638 * 31639 * @animations 31640 * | Animation | Occurs | 31641 * |----------------------------------|-------------------------------------| 31642 * | {@link $animate#addClass addClass} `.ng-hide` | after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden | 31643 * | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngShow` expression evaluates to a truthy value and just before contents are set to visible | 31644 * 31645 * @element ANY 31646 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 31647 * then the element is shown or hidden respectively. 31648 * 31649 * @example 31650 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 31651 <file name="index.html"> 31652 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 31653 <div> 31654 Show: 31655 <div class="check-element animate-show" ng-show="checked">
31656 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 31657 </div> 31658 </div> 31659 <div> 31660 Hide: 31661 <div class="check-element animate-show" ng-hide="checked"> 31662 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 31663 </div> 31664 </div> 31665 </file> 31666 <file name="glyphicons.css"> 31667 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 31668 </file> 31669 <file name="animations.css"> 31670 .animate-show { 31671 line-height: 20px; 31672 opacity: 1; 31673 padding: 10px; 31674 border: 1px solid black; 31675 background: white; 31676 } 31677 31678 .animate-show.ng-hide-add, .animate-show.ng-hide-remove { 31679 transition: all linear 0.5s; 31680 } 31681 31682 .animate-show.ng-hide { 31683 line-height: 0; 31684 opacity: 0; 31685 padding: 0 10px; 31686 } 31687 31688 .check-element { 31689 padding: 10px; 31690 border: 1px solid black; 31691 background: white; 31692 } 31693 </file> 31694 <file name="protractor.js" type="protractor"> 31695 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 31696 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 31697 31698 it('should check ng-show / ng-hide', function() { 31699 expect(thumbsUp.isDisplayed()).toBeFalsy(); 31700 expect(thumbsDown.isDisplayed()).toBeTruthy(); 31701 31702 element(by.model('checked')).click(); 31703 31704 expect(thumbsUp.isDisplayed()).toBeTruthy(); 31705 expect(thumbsDown.isDisplayed()).toBeFalsy(); 31706 }); 31707 </file> 31708 </example> 31709 */ 31710var ngShowDirective = ['$animate', function($animate) { 31711 return { 31712 restrict: 'A', 31713 multiElement: true, 31714 link: function(scope, element, attr) { 31715 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 31716 // we're adding a temporary, animation-specific class for ng-hide since this way 31717 // we can control when the element is actually displayed on screen without having 31718 // to have a global/greedy CSS selector that breaks when other animations are run. 31719 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 31720 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 31721 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 31722 }); 31723 }); 31724 } 31725 }; 31726}]; 31727 31728 31729/** 31730 * @ngdoc directive 31731 * @name ngHide 31732 * @multiElement 31733 * 31734 * @description 31735 * The `ngHide` directive shows or hides the given HTML element based on the expression 31736 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 31737 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 31738 * in AngularJS and sets the display style to none (using an !important flag). 31739 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 31740 * 31741 * ```html 31742 * <!-- when $scope.myValue is truthy (element is hidden) --> 31743 * <div ng-hide="myValue" class="ng-hide"></div> 31744 * 31745 * <!-- when $scope.myValue is falsy (element is visible) --> 31746 * <div ng-hide="myValue"></div> 31747 * ``` 31748 * 31749 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 31750 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 31751 * from the element causing the element not to appear hidden. 31752 * 31753 * ## Why is !important used? 31754 * 31755 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 31756 * can be easily overridden by heavier selectors. For example, something as simple 31757 * as changing the display style on a HTML list item would make hidden elements appear visible. 31758 * This also becomes a bigger issue when dealing with CSS frameworks. 31759 * 31760 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 31761 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 31762 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 31763 * 31764 * ### Overriding `.ng-hide` 31765 * 31766 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 31767 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 31768 * class in CSS: 31769 * 31770 * ```css 31771 * .ng-hide { 31772 * /* this is just another form of hiding an element */ 31773 * display: block!important; 31774 * position: absolute; 31775 * top: -9999px; 31776 * left: -9999px; 31777 * } 31778 * ``` 31779 * 31780 * By default you don't need to override in CSS anything and the animations will work around the display style. 31781 * 31782 * ## A note about animations with `ngHide` 31783 * 31784 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 31785 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 31786 * CSS class is added and removed for you instead of your own CSS class. 31787 * 31788 * ```css 31789 * // 31790 * //a working example can be found at the bottom of this page 31791 * // 31792 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 31793 * transition: 0.5s linear all; 31794 * } 31795 * 31796 * .my-element.ng-hide-add { ... } 31797 * .my-element.ng-hide-add.ng-hide-add-active { ... } 31798 * .my-element.ng-hide-remove { ... } 31799 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 31800 * ``` 31801 * 31802 * Keep in mind that, as of AngularJS version 1.3, there is no need to change the display 31803 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 31804 * 31805 * @animations 31806 * | Animation | Occurs | 31807 * |----------------------------------|-------------------------------------| 31808 * | {@link $animate#addClass addClass} `.ng-hide` | after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden | 31809 * | {@link $animate#removeClass removeClass} `.ng-hide` | after the `ngHide` expression evaluates to a non truthy value and just before contents are set to visible | 31810 * 31811 * 31812 * @element ANY 31813 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 31814 * the element is shown or hidden respectively. 31815 * 31816 * @example 31817 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 31818 <file name="index.html"> 31819 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 31820 <div> 31821 Show: 31822 <div class="check-element animate-hide" ng-show="checked">
31823 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 31824 </div> 31825 </div> 31826 <div> 31827 Hide: 31828 <div class="check-element animate-hide" ng-hide="checked"> 31829 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 31830 </div> 31831 </div> 31832 </file> 31833 <file name="glyphicons.css"> 31834 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 31835 </file> 31836 <file name="animations.css"> 31837 .animate-hide { 31838 transition: all linear 0.5s; 31839 line-height: 20px; 31840 opacity: 1; 31841 padding: 10px; 31842 border: 1px solid black; 31843 background: white; 31844 } 31845 31846 .animate-hide.ng-hide { 31847 line-height: 0; 31848 opacity: 0; 31849 padding: 0 10px; 31850 } 31851 31852 .check-element { 31853 padding: 10px; 31854 border: 1px solid black; 31855 background: white; 31856 } 31857 </file> 31858 <file name="protractor.js" type="protractor"> 31859 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 31860 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 31861 31862 it('should check ng-show / ng-hide', function() { 31863 expect(thumbsUp.isDisplayed()).toBeFalsy(); 31864 expect(thumbsDown.isDisplayed()).toBeTruthy(); 31865 31866 element(by.model('checked')).click(); 31867 31868 expect(thumbsUp.isDisplayed()).toBeTruthy(); 31869 expect(thumbsDown.isDisplayed()).toBeFalsy(); 31870 }); 31871 </file> 31872 </example> 31873 */ 31874var ngHideDirective = ['$animate', function($animate) { 31875 return { 31876 restrict: 'A', 31877 multiElement: true, 31878 link: function(scope, element, attr) { 31879 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 31880 // The comment inside of the ngShowDirective explains why we add and 31881 // remove a temporary class for the show/hide animation 31882 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 31883 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 31884 }); 31885 }); 31886 } 31887 }; 31888}]; 31889 31890/** 31891 * @ngdoc directive 31892 * @name ngStyle 31893 * @restrict AC 31894 * 31895 * @description 31896 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 31897 * 31898 * @element ANY 31899 * @param {expression} ngStyle 31900 * 31901 * {@link guide/expression Expression} which evals to an 31902 * object whose keys are CSS style names and values are corresponding values for those CSS 31903 * keys. 31904 * 31905 * Since some CSS style names are not valid keys for an object, they must be quoted. 31906 * See the 'background-color' style in the example below. 31907 * 31908 * @example 31909 <example> 31910 <file name="index.html"> 31911 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 31912 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 31913 <input type="button" value="clear" ng-click="myStyle={}"> 31914 <br/> 31915 <span ng-style="myStyle">Sample Text</span> 31916 <pre>myStyle={{myStyle}}</pre> 31917 </file> 31918 <file name="style.css"> 31919 span { 31920 color: black; 31921 } 31922 </file> 31923 <file name="protractor.js" type="protractor"> 31924 var colorSpan = element(by.css('span')); 31925 31926 it('should check ng-style', function() { 31927 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 31928 element(by.css('input[value=\'set color\']')).click(); 31929 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 31930 element(by.css('input[value=clear]')).click(); 31931 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 31932 }); 31933 </file> 31934 </example> 31935 */ 31936var ngStyleDirective = ngDirective(function(scope, element, attr) { 31937 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 31938 if (oldStyles && (newStyles !== oldStyles)) { 31939 forEach(oldStyles, function(val, style) { element.css(style, '');}); 31940 } 31941 if (newStyles) element.css(newStyles); 31942 }, true); 31943}); 31944 31945/** 31946 * @ngdoc directive 31947 * @name ngSwitch 31948 * @restrict EA 31949 * 31950 * @description 31951 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 31952 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 31953 * as specified in the template. 31954 * 31955 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 31956 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 31957 * matches the value obtained from the evaluated expression. In other words, you define a container element 31958 * (where you place the directive), place an expression on the **`on="..."` attribute** 31959 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 31960 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 31961 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 31962 * attribute is displayed. 31963 * 31964 * <div class="alert alert-info"> 31965 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 31966 * as literal string values to match against. 31967 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
31967omeVal"` not against the 31968 * value of the expression `$scope.someVal`. 31969 * </div> 31970 31971 * @animations 31972 * | Animation | Occurs | 31973 * |----------------------------------|-------------------------------------| 31974 * | {@link ng.$animate#enter enter} | after the ngSwitch contents change and the matched child element is placed inside the container | 31975 * | {@link ng.$animate#leave leave} | after the ngSwitch contents change and just before the former contents are removed from the DOM | 31976 * 31977 * @usage 31978 * 31979 * ``` 31980 * <ANY ng-switch="expression"> 31981 * <ANY ng-switch-when="matchValue1">...</ANY> 31982 * <ANY ng-switch-when="matchValue2">...</ANY> 31983 * <ANY ng-switch-default>...</ANY> 31984 * </ANY> 31985 * ``` 31986 * 31987 * 31988 * @scope 31989 * @priority 1200 31990 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 31991 * On child elements add: 31992 * 31993 * * `ngSwitchWhen`: the case statement to match against. If match then this 31994 * case will be displayed. If the same match appears multiple times, all the 31995 * elements will be displayed. 31996 * * `ngSwitchDefault`: the default case when no other case match. If there 31997 * are multiple default cases, all of them will be displayed when no other 31998 * case match. 31999 * 32000 * 32001 * @example 32002 <example module="switchExample" deps="angular-animate.js" animations="true"> 32003 <file name="index.html">
32004 <div ng-controller="ExampleController"> 32005 <select ng-model="selection" ng-options="item for item in items"> 32006 </select> 32007 <code>selection={{selection}}</code> 32008 <hr/> 32009 <div class="animate-switch-container" 32010 ng-switch on="selection"> 32011 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 32012 <div class="animate-switch" ng-switch-when="home">Home Span</div> 32013 <div class="animate-switch" ng-switch-default>default</div> 32014 </div> 32015 </div> 32016 </file> 32017 <file name="script.js"> 32018 angular.module('switchExample', ['ngAnimate']) 32019 .controller('ExampleController', ['$scope', function($scope) { 32020 $scope.items = ['settings', 'home', 'other']; 32021 $scope.selection = $scope.items[0]; 32022 }]); 32023 </file> 32024 <file name="animations.css"> 32025 .animate-switch-container { 32026 position:relative; 32027 background:white; 32028 border:1px solid black; 32029 height:40px; 32030 overflow:hidden; 32031 } 32032 32033 .animate-switch { 32034 padding:10px; 32035 } 32036 32037 .animate-switch.ng-animate { 32038 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 32039 32040 position:absolute; 32041 top:0; 32042 left:0; 32043 right:0; 32044 bottom:0; 32045 } 32046 32047 .animate-switch.ng-leave.ng-leave-active, 32048 .animate-switch.ng-enter { 32049 top:-50px; 32050 } 32051 .animate-switch.ng-leave, 32052 .animate-switch.ng-enter.ng-enter-active { 32053 top:0; 32054 } 32055 </file> 32056 <file name="protractor.js" type="protractor"> 32057 var switchElem = element(by.css('[ng-switch]')); 32058 var select = element(by.model('selection')); 32059 32060 it('should start in settings', function() { 32061 expect(switchElem.getText()).toMatch(/Settings Div/); 32062 }); 32063 it('should change to home', function() { 32064 select.all(by.css('option')).get(1).click(); 32065 expect(switchElem.getText()).toMatch(/Home Span/); 32066 }); 32067 it('should select default', function() { 32068 select.all(by.css('option')).get(2).click(); 32069 expect(switchElem.getText()).toMatch(/default/); 32070 }); 32071 </file> 32072 </example> 32073 */ 32074var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { 32075 return { 32076 require: 'ngSwitch', 32077 32078 // asks for $scope to fool the BC controller module 32079 controller: ['$scope', function ngSwitchController() { 32080 this.cases = {}; 32081 }], 32082 link: function(scope, element, attr, ngSwitchController) { 32083 var watchExpr = attr.ngSwitch || attr.on, 32084 selectedTranscludes = [], 32085 selectedElements = [], 32086 previousLeaveAnimations = [], 32087 selectedScopes = []; 32088 32089 var spliceFactory = function(array, index) { 32090 return function() { array.splice(index, 1); }; 32091 }; 32092 32093 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 32094 var i, ii; 32095 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 32096 $animate.cancel(previousLeaveAnimations[i]); 32097 } 32098 previousLeaveAnimations.length = 0; 32099 32100 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 32101 var selected = getBlockNodes(selectedElements[i].clone); 32102 selectedScopes[i].$destroy(); 32103 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 32104 promise.then(spliceFactory(previousLeaveAnimations, i)); 32105 } 32106 32107 selectedElements.length = 0; 32108 selectedScopes.length = 0; 32109 32110 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 32111 forEach(selectedTranscludes, function(selectedTransclude) { 32112 selectedTransclude.transclude(function(caseElement, selectedScope) { 32113 selectedScopes.push(selectedScope); 32114 var anchor = selectedTransclude.element; 32115 caseElement[caseElement.length++] = $compile.$$createComment('end ngSwitchWhen'); 32116 var block = { clone: caseElement }; 32117 32118 selectedElements.push(block); 32119 $animate.enter(caseElement, anchor.parent(), anchor); 32120 }); 32121 }); 32122 } 32123 }); 32124 } 32125 }; 32126}]; 32127 32128var ngSwitchWhenDirective = ngDirective({ 32129 transclude: 'element', 32130 priority: 1200, 32131 require: '^ngSwitch', 32132 multiElement: true, 32133 link: function(scope, element, attrs, ctrl, $transclude) { 32134 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 32135 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 32136 } 32137}); 32138 32139var ngSwitchDefaultDirective = ngDirective({ 32140 transclude: 'element', 32141 priority: 1200, 32142 require: '^ngSwitch', 32143 multiElement: true, 32144 link: function(scope, element, attr, ctrl, $transclude) { 32145 ctrl.cases['?'] = (ctrl.cases['?'] || []); 32146 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 32147 } 32148}); 32149 32150/** 32151 * @ngdoc directive 32152 * @name ngTransclude 32153 * @restrict EAC 32154 * 32155 * @description 32156 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 32157 * 32158 * You can specify that you want to insert a named transclusion slot, instead of the default slot, by providing the slot name 32159 * as the value of the `ng-transclude` or `ng-transclude-slot` attribute. 32160 * 32161 * If the transcluded content is not empty (i.e. contains one or more DOM nodes, including whitespace text nodes), any existing 32162 * content of this element will be removed before the transcluded content is inserted. 32163 * If the transcluded content is empty, the existing content is left intact. This lets you provide fallback content in the case 32164 * that no transcluded content is provided. 32165 * 32166 * @element ANY 32167 * 32168 * @param {string} ngTransclude|ngTranscludeSlot the name of the slot to insert at this point. If this is not provided, is empty 32169 * or its value is the same as the name of the attribute then the default slot is used. 32170 * 32171 * @example 32172 * ### Basic transclusion 32173 * This example demonstrates basic transclusion of content into a component directive. 32174 * <example name="simpleTranscludeExample" module="transcludeExample"> 32175 * <file name="index.html"> 32176 * <script> 32177 * angular.module('transcludeExample', []) 32178 * .directive('pane', function(){ 32179 * return { 32180 * restrict: 'E', 32181 * transclude: true, 32182 * scope: { title:'@' }, 32183 * template: '<div style="border: 1px solid black;">
32183' + 32184 * '<div style="background-color: gray">{{title}}</div>' + 32185 * '<ng-transclude></ng-transclude>' + 32186 * '</div>' 32187 * }; 32188 * }) 32189 * .controller('ExampleController', ['$scope', function($scope) { 32190 * $scope.title = 'Lorem Ipsum'; 32191 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 32192 * }]); 32193 * </script> 32194 * <div ng-controller="ExampleController"> 32195 * <input ng-model="title" aria-label="title"> <br/> 32196 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 32197 * <pane title="{{title}}">{{text}}</pane> 32198 * </div> 32199 * </file> 32200 * <file name="protractor.js" type="protractor"> 32201 * it('should have transcluded', function() { 32202 * var titleElement = element(by.model('title')); 32203 * titleElement.clear(); 32204 * titleElement.sendKeys('TITLE'); 32205 * var textElement = element(by.model('text')); 32206 * textElement.clear(); 32207 * textElement.sendKeys('TEXT'); 32208 * expect(element(by.binding('title')).getText()).toEqual('TITLE'); 32209 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 32210 * }); 32211 * </file> 32212 * </example> 32213 * 32214 * @example 32215 * ### Transclude fallback content 32216 * This example shows how to use `NgTransclude` with fallback content, that 32217 * is displayed if no transcluded content is provided. 32218 * 32219 * <example module="transcludeFallbackContentExample"> 32220 * <file name="index.html"> 32221 * <script> 32222 * angular.module('transcludeFallbackContentExample', []) 32223 * .directive('myButton', function(){ 32224 * return { 32225 * restrict: 'E', 32226 * transclude: true, 32227 * scope: true, 32228 * template: '<button style="cursor: pointer;">' + 32229 * '<ng-transclude>' + 32230 * '<b style="color: red;">Button1</b>' + 32231 * '</ng-transclude>' + 32232 * '</button>' 32233 * }; 32234 * }); 32235 * </script> 32236 * <!-- fallback button content --> 32237 * <my-button id="fallback"></my-button> 32238 * <!-- modified button content --> 32239 * <my-button id="modified"> 32240 * <i style="color: green;">Button2</i> 32241 * </my-button> 32242 * </file> 32243 * <file name="protractor.js" type="protractor"> 32244 * it('should have different transclude element content', function() { 32245 * expect(element(by.id('fallback')).getText()).toBe('Button1'); 32246 * expect(element(by.id('modified')).getText()).toBe('Button2'); 32247 * }); 32248 * </file> 32249 * </example> 32250 * 32251 * @example 32252 * ### Multi-slot transclusion 32253 * This example demonstrates using multi-slot transclusion in a component directive. 32254 * <example name="multiSlotTranscludeExample" module="multiSlotTranscludeExample"> 32255 * <file name="index.html"> 32256 * <style> 32257 * .title, .footer { 32258 * background-color: gray 32259 * } 32260 * </style>
32261 * <div ng-controller="ExampleController"> 32262 * <input ng-model="title" aria-label="title"> <br/> 32263 * <textarea ng-model="text" aria-label="text"></textarea> <br/> 32264 * <pane> 32265 * <pane-title><a ng-href="{{link}}">{{title}}</a></pane-title> 32266 * <pane-body><p>{{text}}</p></pane-body> 32267 * </pane> 32268 * </div> 32269 * </file> 32270 * <file name="app.js"> 32271 * angular.module('multiSlotTranscludeExample', []) 32272 * .directive('pane', function(){ 32273 * return { 32274 * restrict: 'E', 32275 * transclude: { 32276 * 'title': '?paneTitle', 32277 * 'body': 'paneBody', 32278 * 'footer': '?paneFooter' 32279 * }, 32280 * template: '<div style="border: 1px solid black;">' + 32281 * '<div class="title" ng-transclude="title">Fallback Title</div>' + 32282 * '<div ng-transclude="body"></div>' + 32283 * '<div class="footer" ng-transclude="footer">Fallback Footer</div>' + 32284 * '</div>' 32285 * }; 32286 * }) 32287 * .controller('ExampleController', ['$scope', function($scope) { 32288 * $scope.title = 'Lorem Ipsum'; 32289 * $scope.link = "https://google.com"; 32290 * $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 32291 * }]); 32292 * </file> 32293 * <file name="protractor.js" type="protractor"> 32294 * it('should have transcluded the title and the body', function() { 32295 * var titleElement = element(by.model('title')); 32296 * titleElement.clear(); 32297 * titleElement.sendKeys('TITLE'); 32298 * var textElement = element(by.model('text')); 32299 * textElement.clear(); 32300 * textElement.sendKeys('TEXT'); 32301 * expect(element(by.css('.title')).getText()).toEqual('TITLE'); 32302 * expect(element(by.binding('text')).getText()).toEqual('TEXT'); 32303 * expect(element(by.css('.footer')).getText()).toEqual('Fallback Footer'); 32304 * }); 32305 * </file> 32306 * </example> 32307 */ 32308var ngTranscludeMinErr = minErr('ngTransclude'); 32309var ngTranscludeDirective = ngDirective({ 32310 restrict: 'EAC', 32311 link: function($scope, $element, $attrs, controller, $transclude) { 32312 32313 if ($attrs.ngTransclude === $attrs.$attr.ngTransclude) { 32314 // If the attribute is of the form: `ng-transclude="ng-transclude"` 32315 // then treat it like the default 32316 $attrs.ngTransclude = ''; 32317 } 32318 32319 function ngTranscludeCloneAttachFn(clone) { 32320 if (clone.length) { 32321 $element.empty(); 32322 $element.append(clone); 32323 } 32324 } 32325 32326 if (!$transclude) { 32327 throw ngTranscludeMinErr('orphan', 32328 'Illegal use of ngTransclude directive in the template! ' + 32329 'No parent directive that requires a transclusion found. ' + 32330 'Element: {0}', 32331 startingTag($element)); 32332 } 32333 32334 // If there is no slot name defined or the slot name is not optional 32335 // then transclude the slot 32336 var slotName = $attrs.ngTransclude || $attrs.ngTranscludeSlot; 32337 $transclude(ngTranscludeCloneAttachFn, null, slotName); 32338 } 32339}); 32340 32341/** 32342 * @ngdoc directive 32343 * @name script 32344 * @restrict E 32345 * 32346 * @description 32347 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 32348 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 32349 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 32350 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 32351 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 32352 * 32353 * @param {string} type Must be set to `'text/ng-template'`. 32354 * @param {string} id Cache name of the template. 32355 * 32356 * @example 32357 <example> 32358 <file name="index.html"> 32359 <script type="text/ng-template" id="/tpl.html"> 32360 Content of the template. 32361 </script> 32362 32363 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 32364 <div id="tpl-content" ng-include src="currentTpl"></div> 32365 </file> 32366 <file name="protractor.js" type="protractor"> 32367 it('should load template defined inside script tag', function() {
32368 element(by.css('#tpl-link')).click(); 32369 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 32370 }); 32371 </file> 32372 </example> 32373 */ 32374var scriptDirective = ['$templateCache', function($templateCache) { 32375 return { 32376 restrict: 'E', 32377 terminal: true, 32378 compile: function(element, attr) { 32379 if (attr.type == 'text/ng-template') { 32380 var templateUrl = attr.id, 32381 text = element[0].text; 32382 32383 $templateCache.put(templateUrl, text); 32384 } 32385 } 32386 }; 32387}]; 32388 32389var noopNgModelController = { $setViewValue: noop, $render: noop }; 32390 32391function chromeHack(optionElement) { 32392 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 32393 // Adding an <option selected="selected"> element to a <select required="required"> should 32394 // automatically select the new element 32395 if (optionElement[0].hasAttribute('selected')) { 32396 optionElement[0].selected = true; 32397 } 32398} 32399 32400/** 32401 * @ngdoc type 32402 * @name select.SelectController 32403 * @description 32404 * The controller for the `<select>` directive. This provides support for reading 32405 * and writing the selected value(s) of the control and also coordinates dynamically 32406 * added `<option>` elements, perhaps by an `ngRepeat` directive. 32407 */ 32408var SelectController = 32409 ['$element', '$scope', function($element, $scope) { 32410 32411 var self = this, 32412 optionsMap = new HashMap(); 32413 32414 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 32415 self.ngModelCtrl = noopNgModelController; 32416 32417 // The "unknown" option is one that is prepended to the list if the viewValue 32418 // does not match any of the options. When it is rendered the value of the unknown 32419 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 32420 // 32421 // We can't just jqLite('<option>') since jqLite is not smart enough 32422 // to create it in <select> and IE barfs otherwise. 32423 self.unknownOption = jqLite(window.document.createElement('option')); 32424 self.renderUnknownOption = function(val) { 32425 var unknownVal = '? ' + hashKey(val) + ' ?'; 32426 self.unknownOption.val(unknownVal); 32427 $element.prepend(self.unknownOption); 32428 $element.val(unknownVal); 32429 }; 32430 32431 $scope.$on('$destroy', function() { 32432 // disable unknown option so that we don't do work when the whole select is being destroyed 32433 self.renderUnknownOption = noop; 32434 }); 32435 32436 self.removeUnknownOption = function() { 32437 if (self.unknownOption.parent()) self.unknownOption.remove(); 32438 }; 32439 32440 32441 // Read the value of the select control, the implementation of this changes depending 32442 // upon whether the select can have multiple values and whether ngOptions is at work. 32443 self.readValue = function readSingleValue() { 32444 self.removeUnknownOption(); 32445 return $element.val(); 32446 }; 32447 32448 32449 // Write the value to the select control, the implementation of this changes depending 32450 // upon whether the select can have multiple values and whether ngOptions is at work. 32451 self.writeValue = function writeSingleValue(value) { 32452 if (self.hasOption(value)) { 32453 self.removeUnknownOption(); 32454 $element.val(value); 32455 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 32456 } else { 32457 if (value == null && self.emptyOption) { 32458 self.removeUnknownOption(); 32459 $element.val(''); 32460 } else { 32461 self.renderUnknownOption(value); 32462 } 32463 } 32464 }; 32465 32466 32467 // Tell the select control that an option, with the given value, has been added 32468 self.addOption = function(value, element) { 32469 // Skip comment nodes, as they only pollute the `optionsMap` 32470 if (element[0].nodeType === NODE_TYPE_COMMENT) return; 32471 32472 assertNotHasOwnProperty(value, '"option value"'); 32473 if (value === '') { 32474 self.emptyOption = element; 32475 } 32476 var count = optionsMap.get(value) || 0; 32477 optionsMap.put(value, count + 1); 32478 self.ngModelCtrl.$render(); 32479 chromeHack(element); 32480 }; 32481 32482 // Tell the select control that an option, with the given value, has been removed 32483 self.removeOption = function(value) { 32484 var count = optionsMap.get(value); 32485 if (count) { 32486 if (count === 1) { 32487 optionsMap.remove(value); 32488 if (value === '') { 32489 self.emptyOption = undefined; 32490 } 32491 } else { 32492 optionsMap.put(value, count - 1); 32493 } 32494 } 32495 }; 32496 32497 // Check whether the select control has an option matching the given value 32498 self.hasOption = function(value) { 32499 return !!optionsMap.get(value); 32500 }; 32501 32502 32503 self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) { 32504 32505 if (interpolateValueFn) { 32506 // The value attribute is interpolated 32507 var oldVal; 32508 optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) { 32509 if (isDefined(oldVal)) { 32510 self.removeOption(oldVal); 32511 } 32512 oldVal = newVal; 32513 self.addOption(newVal, optionElement); 32514 }); 32515 } else if (interpolateTextFn) { 32516 // The text content is interpolated 32517 optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) { 32518 optionAttrs.$set('value', newVal); 32519 if (oldVal !== newVal) { 32520 self.removeOption(oldVal); 32521 } 32522 self.addOption(newVal, optionElement); 32523 }); 32524 } else { 32525 // The value attribute is static 32526 self.addOption(optionAttrs.value, optionElement); 32527 } 32528 32529 optionElement.on('$destroy', function() { 32530 self.removeOption(optionAttrs.value); 32531 self.ngModelCtrl.$render(); 32532 }); 32533 }; 32534}]; 32535 32536/** 32537 * @ngdoc directive 32538 * @name select 32539 * @restrict E 32540 * 32541 * @description 32542 * HTML `SELECT` element with angular data-binding. 32543 * 32544 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 32545 * between the scope and the `<select>` control (including setting default values). 32546 * It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 32547 * {@link ngOptions `ngOptions`} directives. 32548 * 32549 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 32550 * to the model identified by the `ngModel` directive. With static or repeated o
32550ptions, this is 32551 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 32552 * If you want dynamic value attributes, you can use interpolation inside the value attribute. 32553 * 32554 * <div class="alert alert-warning"> 32555 * Note that the value of a `select` directive used without `ngOptions` is always a string. 32556 * When the model needs to be bound to a non-string value, you must either explicitly convert it 32557 * using a directive (see example below) or use `ngOptions` to specify the set of options. 32558 * This is because an option element can only be bound to string values at present. 32559 * </div> 32560 * 32561 * If the viewValue of `ngModel` does not match any of the options, then the control 32562 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 32563 * 32564 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 32565 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 32566 * option. See example below for demonstration. 32567 * 32568 * <div class="alert alert-info"> 32569 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 32570 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits, such as 32571 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 32572 * comprehension expression, and additionally in reducing memory and increasing speed by not creating 32573 * a new scope for each repeated instance. 32574 * </div> 32575 * 32576 * 32577 * @param {string} ngModel Assignable angular expression to data-bind to. 32578 * @param {string=} name Property name of the form under which the control is published. 32579 * @param {string=} multiple Allows multiple options to be selected. The selected values will be 32580 * bound to the model as an array. 32581 * @param {string=} required Sets `required` validation error key if the value is not entered. 32582 * @param {string=} ngRequired Adds required attribute and required validation constraint to 32583 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 32584 * when you want to data-bind to the required attribute. 32585 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user 32586 * interaction with the select element. 32587 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 32588 * set on the model on selection. See {@link ngOptions `ngOptions`}. 32589 * 32590 * @example 32591 * ### Simple `select` elements with static options 32592 * 32593 * <example name="static-select" module="staticSelect"> 32594 * <file name="index.html">
32595 * <div ng-controller="ExampleController"> 32596 * <form name="myForm"> 32597 * <label for="singleSelect"> Single select: </label><br> 32598 * <select name="singleSelect" ng-model="data.singleSelect"> 32599 * <option value="option-1">Option 1</option> 32600 * <option value="option-2">Option 2</option> 32601 * </select><br> 32602 * 32603 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 32604 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 32605 * <option value="">---Please select---</option> <!-- not selected / blank option --> 32606 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 32607 * <option value="option-2">Option 2</option> 32608 * </select><br> 32609 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 32610 * <tt>singleSelect = {{data.singleSelect}}</tt> 32611 * 32612 * <hr> 32613 * <label for="multipleSelect"> Multiple select: </label><br> 32614 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 32615 * <option value="option-1">Option 1</option> 32616 * <option value="option-2">Option 2</option> 32617 * <option value="option-3">Option 3</option> 32618 * </select><br> 32619 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 32620 * </form> 32621 * </div> 32622 * </file> 32623 * <file name="app.js"> 32624 * angular.module('staticSelect', []) 32625 * .controller('ExampleController', ['$scope', function($scope) { 32626 * $scope.data = { 32627 * singleSelect: null, 32628 * multipleSelect: [], 32629 * option1: 'option-1', 32630 * }; 32631 * 32632 * $scope.forceUnknownOption = function() { 32633 * $scope.data.singleSelect = 'nonsense'; 32634 * }; 32635 * }]); 32636 * </file> 32637 *</example> 32638 * 32639 * ### Using `ngRepeat` to generate `select` options 32640 * <example name="ngrepeat-select" module="ngrepeatSelect"> 32641 * <file name="index.html">
32642 * <div ng-controller="ExampleController"> 32643 * <form name="myForm"> 32644 * <label for="repeatSelect"> Repeat select: </label> 32645 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> 32646 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 32647 * </select> 32648 * </form> 32649 * <hr> 32650 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> 32651 * </div> 32652 * </file> 32653 * <file name="app.js"> 32654 * angular.module('ngrepeatSelect', []) 32655 * .controller('ExampleController', ['$scope', function($scope) { 32656 * $scope.data = { 32657 * repeatSelect: null, 32658 * availableOptions: [ 32659 * {id: '1', name: 'Option A'}, 32660 * {id: '2', name: 'Option B'}, 32661 * {id: '3', name: 'Option C'} 32662 * ], 32663 * }; 32664 * }]); 32665 * </file> 32666 *</example> 32667 * 32668 * 32669 * ### Using `select` with `ngOptions` and setting a default value 32670 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 32671 * 32672 * <example name="select-with-default-values" module="defaultValueSelect"> 32673 * <file name="index.html">
32674 * <div ng-controller="ExampleController"> 32675 * <form name="myForm"> 32676 * <label for="mySelect">Make a choice:</label> 32677 * <select name="mySelect" id="mySelect" 32678 * ng-options="option.name for option in data.availableOptions track by option.id" 32679 * ng-model="data.selectedOption"></select> 32680 * </form> 32681 * <hr> 32682 * <tt>option = {{data.selectedOption}}</tt><br/> 32683 * </div> 32684 * </file> 32685 * <file name="app.js"> 32686 * angular.module('defaultValueSelect', []) 32687 * .controller('ExampleController', ['$scope', function($scope) { 32688 * $scope.data = { 32689 * availableOptions: [ 32690 * {id: '1', name: 'Option A'}, 32691 * {id: '2', name: 'Option B'}, 32692 * {id: '3', name: 'Option C'} 32693 * ], 32694 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 32695 * }; 32696 * }]); 32697 * </file> 32698 *</example> 32699 * 32700 * 32701 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 32702 * 32703 * <example name="select-with-non-string-options" module="nonStringSelect"> 32704 * <file name="index.html"> 32705 * <select ng-model="model.id" convert-to-number> 32706 * <option value="0">Zero</option> 32707 * <option value="1">One</option> 32708 * <option value="2">Two</option> 32709 * </select> 32710 * {{ model }} 32711 * </file> 32712 * <file name="app.js"> 32713 * angular.module('nonStringSelect', []) 32714 * .run(function($rootScope) { 32715 * $rootScope.model = { id: 2 }; 32716 * }) 32717 * .directive('convertToNumber', function() { 32718 * return { 32719 * require: 'ngModel', 32720 * link: function(scope, element, attrs, ngModel) { 32721 * ngModel.$parsers.push(function(val) { 32722 * return parseInt(val, 10); 32723 * }); 32724 * ngModel.$formatters.push(function(val) { 32725 * return '' + val; 32726 * }); 32727 * } 32728 * }; 32729 * }); 32730 * </file> 32731 * <file name="protractor.js" type="protractor"> 32732 * it('should initialize to model', function() { 32733 * var select = element(by.css('select')); 32734 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 32735 * }); 32736 * </file> 32737 * </example> 32738 * 32739 */ 32740var selectDirective = function() { 32741 32742 return { 32743 restrict: 'E', 32744 require: ['select', '?ngModel'], 32745 controller: SelectController, 32746 priority: 1, 32747 link: { 32748 pre: selectPreLink, 32749 post: selectPostLink 32750 } 32751 }; 32752 32753 function selectPreLink(scope, element, attr, ctrls) { 32754 32755 // if ngModel is not defined, we don't need to do anything 32756 var ngModelCtrl = ctrls[1]; 32757 if (!ngModelCtrl) return; 32758 32759 var selectCtrl = ctrls[0]; 32760 32761 selectCtrl.ngModelCtrl = ngModelCtrl; 32762 32763 // When the selected item(s) changes we delegate getting the value of the select control 32764 // to the `readValue` method, which can be changed if the select can have multiple 32765 // selected values or if the options are being generated by `ngOptions` 32766 element.on('change', function() { 32767 scope.$apply(function() { 32768 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 32769 }); 32770 }); 32771 32772 // If the select allows multiple values then we need to modify how we read and write 32773 // values from and to the control; also what it means for the value to be empty and 32774 // we have to add an extra watch since ngModel doesn't work well with arrays - it 32775 // doesn't trigger rendering if only an item in the array changes. 32776 if (attr.multiple) { 32777 32778 // Read value now needs to check each option to see if it is selected 32779 selectCtrl.readValue = function readMultipleValue() { 32780 var array = []; 32781 forEach(element.find('option'), function(option) { 32782 if (option.selected) { 32783 array.push(option.value); 32784 } 32785 }); 32786 return array; 32787 }; 32788 32789 // Write value now needs to set the selected property of each matching option 32790 selectCtrl.writeValue = function writeMultipleValue(value) { 32791 var items = new HashMap(value); 32792 forEach(element.find('option'), function(option) { 32793 option.selected = isDefined(items.get(option.value)); 32794 }); 32795 }; 32796 32797 // we have to do it on each watch since ngModel watches reference, but 32798 // we need to work of an array, so we need to see if anything was inserted/removed 32799 var lastView, lastViewRef = NaN; 32800 scope.$watch(function selectMultipleWatch() { 32801 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 32802 lastView = shallowCopy(ngModelCtrl.$viewValue); 32803 ngModelCtrl.$render(); 32804 } 32805 lastViewRef = ngModelCtrl.$viewValue; 32806 }); 32807 32808 // If we are a multiple select then value is now a collection 32809 // so the meaning of $isEmpty changes 32810 ngModelCtrl.$isEmpty = function(value) { 32811 return !value || value.length === 0; 32812 }; 32813 32814 } 32815 } 32816 32817 function selectPostLink(scope, element, attrs, ctrls) { 32818 // if ngModel is not defined, we don't need to do anything 32819 var ngModelCtrl = ctrls[1]; 32820 if (!ngModelCtrl) return; 32821 32822 var selectCtrl = ctrls[0]; 32823 32824 // We delegate rendering to the `writeValue` method, which can be changed 32825 // if the select can have multiple selected values or if the options are being 32826 // generated by `ngOptions`. 32827 // This must be done in the postLink fn to prevent $render to be called before 32828 // all nodes have been linked correctly. 32829 ngModelCtrl.$render = function() { 32830 selectCtrl.writeValue(ngModelCtrl.$viewValue); 32831 }; 32832 } 32833}; 32834 32835 32836// The option directive is purely designed to communicate the existence (or lack of) 32837// of dynamically created (and destroyed) option elements to their containing select
vendor: 2,836 bytes, lines 32838-32906
32838// directive via its controller. 32839var optionDirective = ['$interpolate', function($interpolate) { 32840 return { 32841 restrict: 'E', 32842 priority: 100, 32843 compile: function(element, attr) { 32844 if (isDefined(attr.value)) { 32845 // If the value attribute is defined, check if it contains an interpolation 32846 var interpolateValueFn = $interpolate(attr.value, true); 32847 } else { 32848 // If the value attribute is not defined then we fall back to the 32849 // text content of the option element, which may be interpolated 32850 var interpolateTextFn = $interpolate(element.text(), true); 32851 if (!interpolateTextFn) { 32852 attr.$set('value', element.text()); 32853 } 32854 } 32855 32856 return function(scope, element, attr) { 32857 // This is an optimization over using ^^ since we don't want to have to search 32858 // all the way to the root of the DOM for every single option element 32859 var selectCtrlName = '$selectController', 32860 parent = element.parent(), 32861 selectCtrl = parent.data(selectCtrlName) || 32862 parent.parent().data(selectCtrlName); // in case we are in optgroup 32863 32864 if (selectCtrl) { 32865 selectCtrl.registerOption(scope, element, attr, interpolateValueFn, interpolateTextFn); 32866 } 32867 }; 32868 } 32869 }; 32870}]; 32871 32872var styleDirective = valueFn({ 32873 restrict: 'E', 32874 terminal: false 32875}); 32876 32877/** 32878 * @ngdoc directive 32879 * @name ngRequired 32880 * 32881 * @description 32882 * 32883 * ngRequired adds the required {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 32884 * It is most often used for {@link input `input`} and {@link select `select`} controls, but can also be 32885 * applied to custom controls. 32886 * 32887 * The directive sets the `required` attribute on the element if the Angular expression inside 32888 * `ngRequired` evaluates to true. A special directive for setting `required` is necessary because we 32889 * cannot use interpolation inside `required`. See the {@link guide/interpolation interpolation guide} 32890 * for more info. 32891 * 32892 * The validator will set the `required` error key to true if the `required` attribute is set and 32893 * calling {@link ngModel.NgModelController#$isEmpty `NgModelController.$isEmpty`} with the 32894 * {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} returns `true`. For example, the 32895 * `$isEmpty()` implementation for `input[text]` checks the length of the `$viewValue`. When developing 32896 * custom controls, `$isEmpty()` can be overwritten to account for a $viewValue that is not string-based. 32897 * 32898 * @example 32899 * <example name="ngRequiredDirective" module="ngRequiredExample"> 32900 * <file name="index.html"> 32901 * <script> 32902 * angular.module('ngRequiredExample', []) 32903 * .controller('ExampleController', ['$scope', function($scope) { 32904 * $scope.required = true; 32905 * }]); 32906 * </script>
32907 * <div ng-controller="ExampleController"> 32908 * <form name="form"> 32909 * <label for="required">Toggle required: </label> 32910 * <input type="checkbox" ng-model="required" id="required" /> 32911 * <br> 32912 * <label for="input">This input must be filled if `required` is true: </label> 32913 * <input type="text" ng-model="model" id="input" name="input" ng-required="required" /><br> 32914 * <hr> 32915 * required error set? = <code>{{form.input.$error.required}}</code><br> 32916 * model = <code>{{model}}</code> 32917 * </form> 32918 * </div> 32919 * </file> 32920 * <file name="protractor.js" type="protractor"> 32921 var required = element(by.binding('form.input.$error.required')); 32922 var model = element(by.binding('model')); 32923 var input = element(by.id('input')); 32924 32925 it('should set the required error', function() { 32926 expect(required.getText()).toContain('true'); 32927 32928 input.sendKeys('123'); 32929 expect(required.getText()).not.toContain('true'); 32930 expect(model.getText()).toContain('123'); 32931 }); 32932 * </file> 32933 * </example> 32934 */ 32935var requiredDirective = function() { 32936 return { 32937 restrict: 'A', 32938 require: '?ngModel', 32939 link: function(scope, elm, attr, ctrl) { 32940 if (!ctrl) return; 32941 attr.required = true; // force truthy in case we are on non input element 32942 32943 ctrl.$validators.required = function(modelValue, viewValue) { 32944 return !attr.required || !ctrl.$isEmpty(viewValue); 32945 }; 32946 32947 attr.$observe('required', function() { 32948 ctrl.$validate(); 32949 }); 32950 } 32951 }; 32952}; 32953 32954/** 32955 * @ngdoc directive 32956 * @name ngPattern 32957 * 32958 * @description 32959 * 32960 * ngPattern adds the pattern {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 32961 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 32962 * 32963 * The validator sets the `pattern` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 32964 * does not match a RegExp which is obtained by evaluating the Angular expression given in the 32965 * `ngPattern` attribute value: 32966 * * If the expression evaluates to a RegExp object, then this is used directly. 32967 * * If the expression evaluates to a string, then it will be converted to a RegExp after wrapping it 32968 * in `^` and `$` characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 32969 * 32970 * <div class="alert alert-info"> 32971 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 32972 * start at the index of the last search's match, thus not taking the whole input value into 32973 * account. 32974 * </div> 32975 * 32976 * <div class="alert alert-info"> 32977 * **Note:** This directive is also added when the plain `pattern` attribute is used, with two 32978 * differences: 32979 * <ol> 32980 * <li> 32981 * `ngPattern` does not set the `pattern` attribute and therefore HTML5 constraint validation is 32982 * not available. 32983 * </li> 32984 * <li> 32985 * The `ngPattern` attribute must be an expression, while the `pattern` value must be 32986 * interpolated. 32987 * </li> 32988 * </ol> 32989 * </div> 32990 * 32991 * @example 32992 * <example name="ngPatternDirective" module="ngPatternExample"> 32993 * <file name="index.html"> 32994 * <script> 32995 * angular.module('ngPatternExample', []) 32996 * .controller('ExampleController', ['$scope', function($scope) { 32997 * $scope.regex = '\\d+'; 32998 * }]); 32999 * </script>
33000 * <div ng-controller="ExampleController"> 33001 * <form name="form"> 33002 * <label for="regex">Set a pattern (regex string): </label> 33003 * <input type="text" ng-model="regex" id="regex" /> 33004 * <br> 33005 * <label for="input">This input is restricted by the current pattern: </label> 33006 * <input type="text" ng-model="model" id="input" name="input" ng-pattern="regex" /><br> 33007 * <hr> 33008 * input valid? = <code>{{form.input.$valid}}</code><br> 33009 * model = <code>{{model}}</code> 33010 * </form> 33011 * </div> 33012 * </file> 33013 * <file name="protractor.js" type="protractor"> 33014 var model = element(by.binding('model')); 33015 var input = element(by.id('input')); 33016 33017 it('should validate the input with the default pattern', function() { 33018 input.sendKeys('aaa'); 33019 expect(model.getText()).not.toContain('aaa'); 33020 33021 input.clear().then(function() { 33022 input.sendKeys('123'); 33023 expect(model.getText()).toContain('123'); 33024 }); 33025 }); 33026 * </file> 33027 * </example> 33028 */ 33029var patternDirective = function() { 33030 return { 33031 restrict: 'A', 33032 require: '?ngModel', 33033 link: function(scope, elm, attr, ctrl) { 33034 if (!ctrl) return; 33035 33036 var regexp, patternExp = attr.ngPattern || attr.pattern; 33037 attr.$observe('pattern', function(regex) { 33038 if (isString(regex) && regex.length > 0) { 33039 regex = new RegExp('^' + regex + '$'); 33040 } 33041 33042 if (regex && !regex.test) { 33043 throw minErr('ngPattern')('noregexp', 33044 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 33045 regex, startingTag(elm)); 33046 } 33047 33048 regexp = regex || undefined; 33049 ctrl.$validate(); 33050 }); 33051 33052 ctrl.$validators.pattern = function(modelValue, viewValue) { 33053 // HTML5 pattern constraint validates the input value, so we validate the viewValue 33054 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 33055 }; 33056 } 33057 }; 33058}; 33059 33060/** 33061 * @ngdoc directive 33062 * @name ngMaxlength 33063 * 33064 * @description 33065 * 33066 * ngMaxlength adds the maxlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 33067 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 33068 * 33069 * The validator sets the `maxlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 33070 * is longer than the integer obtained by evaluating the Angular expression given in the 33071 * `ngMaxlength` attribute value. 33072 * 33073 * <div class="alert alert-info"> 33074 * **Note:** This directive is also added when the plain `maxlength` attribute is used, with two 33075 * differences: 33076 * <ol> 33077 * <li> 33078 * `ngMaxlength` does not set the `maxlength` attribute and therefore HTML5 constraint 33079 * validation is not available. 33080 * </li> 33081 * <li> 33082 * The `ngMaxlength` attribute must be an expression, while the `maxlength` value must be 33083 * interpolated. 33084 * </li> 33085 * </ol> 33086 * </div> 33087 * 33088 * @example 33089 * <example name="ngMaxlengthDirective" module="ngMaxlengthExample"> 33090 * <file name="index.html"> 33091 * <script> 33092 * angular.module('ngMaxlengthExample', []) 33093 * .controller('ExampleController', ['$scope', function($scope) { 33094 * $scope.maxlength = 5; 33095 * }]); 33096 * </script>
33097 * <div ng-controller="ExampleController"> 33098 * <form name="form"> 33099 * <label for="maxlength">Set a maxlength: </label> 33100 * <input type="number" ng-model="maxlength" id="maxlength" /> 33101 * <br> 33102 * <label for="input">This input is restricted by the current maxlength: </label> 33103 * <input type="text" ng-model="model" id="input" name="input" ng-maxlength="maxlength" /><br> 33104 * <hr> 33105 * input valid? = <code>{{form.input.$valid}}</code><br> 33106 * model = <code>{{model}}</code> 33107 * </form> 33108 * </div> 33109 * </file> 33110 * <file name="protractor.js" type="protractor"> 33111 var model = element(by.binding('model')); 33112 var input = element(by.id('input')); 33113 33114 it('should validate the input with the default maxlength', function() { 33115 input.sendKeys('abcdef'); 33116 expect(model.getText()).not.toContain('abcdef'); 33117 33118 input.clear().then(function() { 33119 input.sendKeys('abcde'); 33120 expect(model.getText()).toContain('abcde'); 33121 }); 33122 }); 33123 * </file> 33124 * </example> 33125 */ 33126var maxlengthDirective = function() { 33127 return { 33128 restrict: 'A', 33129 require: '?ngModel', 33130 link: function(scope, elm, attr, ctrl) { 33131 if (!ctrl) return; 33132 33133 var maxlength = -1; 33134 attr.$observe('maxlength', function(value) { 33135 var intVal = toInt(value); 33136 maxlength = isNaN(intVal) ? -1 : intVal; 33137 ctrl.$validate(); 33138 }); 33139 ctrl.$validators.maxlength = function(modelValue, viewValue) { 33140 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 33141 }; 33142 } 33143 }; 33144}; 33145 33146/** 33147 * @ngdoc directive 33148 * @name ngMinlength 33149 * 33150 * @description 33151 * 33152 * ngMinlength adds the minlength {@link ngModel.NgModelController#$validators `validator`} to {@link ngModel `ngModel`}. 33153 * It is most often used for text-based {@link input `input`} controls, but can also be applied to custom text-based controls. 33154 * 33155 * The validator sets the `minlength` error key if the {@link ngModel.NgModelController#$viewValue `ngModel.$viewValue`} 33156 * is shorter than the integer obtained by evaluating the Angular expression given in the 33157 * `ngMinlength` attribute value. 33158 * 33159 * <div class="alert alert-info"> 33160 * **Note:** This directive is also added when the plain `minlength` attribute is used, with two 33161 * differences: 33162 * <ol> 33163 * <li> 33164 * `ngMinlength` does not set the `minlength` attribute and therefore HTML5 constraint 33165 * validation is not available. 33166 * </li> 33167 * <li> 33168 * The `ngMinlength` value must be an expression, while the `minlength` value must be 33169 * interpolated. 33170 * </li> 33171 * </ol> 33172 * </div> 33173 * 33174 * @example 33175 * <example name="ngMinlengthDirective" module="ngMinlengthExample"> 33176 * <file name="index.html"> 33177 * <script> 33178 * angular.module('ngMinlengthExample', []) 33179 * .controller('ExampleController', ['$scope', function($scope) { 33180 * $scope.minlength = 3; 33181 * }]); 33182 * </script>
33183 * <div ng-controller="ExampleController"> 33184 * <form name="form"> 33185 * <label for="minlength">Set a minlength: </label> 33186 * <input type="number" ng-model="minlength" id="minlength" /> 33187 * <br> 33188 * <label for="input">This input is restricted by the current minlength: </label> 33189 * <input type="text" ng-model="model" id="input" name="input" ng-minlength="minlength" /><br> 33190 * <hr> 33191 * input valid? = <code>{{form.input.$valid}}</code><br> 33192 * model = <code>{{model}}</code> 33193 * </form> 33194 * </div> 33195 * </file> 33196 * <file name="protractor.js" type="protractor"> 33197 var model = element(by.binding('model')); 33198 var input = element(by.id('input')); 33199 33200 it('should validate the input with the default minlength', function() { 33201 input.sendKeys('ab'); 33202 expect(model.getText()).not.toContain('ab'); 33203 33204 input.sendKeys('abc'); 33205 expect(model.getText()).toContain('abc'); 33206 }); 33207 * </file> 33208 * </example> 33209 */ 33210var minlengthDirective = function() { 33211 return { 33212 restrict: 'A', 33213 require: '?ngModel', 33214 link: function(scope, elm, attr, ctrl) { 33215 if (!ctrl) return; 33216 33217 var minlength = 0; 33218 attr.$observe('minlength', function(value) { 33219 minlength = toInt(value) || 0; 33220 ctrl.$validate(); 33221 }); 33222 ctrl.$validators.minlength = function(modelValue, viewValue) { 33223 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 33224 }; 33225 } 33226 }; 33227}; 33228 33229if (window.angular.bootstrap) { 33230 //AngularJS is already loaded, so we can return here... 33231 if (window.console) { 33232 console.log('WARNING: Tried to load angular more than once.'); 33233 } 33234 return; 33235} 33236 33237//try to bind to jquery now so that one can write jqLite(document).ready() 33238//but we will rebind on bootstrap again. 33239bindJQuery(); 33240 33241publishExternalAPI(angular); 33242 33243angular.module("ngLocale", [], ["$provide", function($provide) { 33244var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 33245function getDecimals(n) { 33246 n = n + ''; 33247 var i = n.indexOf('.'); 33248 return (i == -1) ? 0 : n.length - i - 1; 33249} 33250 33251function getVF(n, opt_precision) { 33252 var v = opt_precision; 33253 33254 if (undefined === v) { 33255 v = Math.min(getDecimals(n), 3); 33256 } 33257 33258 var base = Math.pow(10, v); 33259 var f = ((n * base) | 0) % base; 33260 return {v: v, f: f}; 33261} 33262 33263$provide.value("$locale", { 33264 "DATETIME_FORMATS": { 33265 "AMPMS": [ 33266 "AM", 33267 "PM" 33268 ], 33269 "DAY": [ 33270 "Sunday", 33271 "Monday", 33272 "Tuesday", 33273 "Wednesday", 33274 "Thursday", 33275 "Friday", 33276 "Saturday" 33277 ], 33278 "ERANAMES": [ 33279 "Before Christ", 33280 "Anno Domini" 33281 ], 33282 "ERAS": [ 33283 "BC", 33284 "AD" 33285 ], 33286 "FIRSTDAYOFWEEK": 6, 33287 "MONTH": [ 33288 "January", 33289 "February", 33290 "March", 33291 "April", 33292 "May", 33293 "June", 33294 "July", 33295 "August", 33296 "September", 33297 "October", 33298 "November", 33299 "December" 33300 ], 33301 "SHORTDAY": [ 33302 "Sun", 33303 "Mon", 33304 "Tue", 33305 "Wed", 33306 "Thu", 33307 "Fri", 33308 "Sat" 33309 ], 33310 "SHORTMONTH": [ 33311 "Jan", 33312 "Feb", 33313 "Mar", 33314 "Apr", 33315 "May", 33316 "Jun", 33317 "Jul", 33318 "Aug", 33319 "Sep", 33320 "Oct", 33321 "Nov", 33322 "Dec" 33323 ], 33324 "STANDALONEMONTH": [ 33325 "January", 33326 "February", 33327 "March", 33328 "April", 33329 "May", 33330 "June", 33331 "July", 33332 "August", 33333 "September", 33334 "October", 33335 "November", 33336 "December" 33337 ], 33338 "WEEKENDRANGE": [ 33339 5, 33340 6 33341 ], 33342 "fullDate": "EEEE, MMMM d, y", 33343 "longDate": "MMMM d, y", 33344 "medium": "MMM d, y h:mm:ss a", 33345 "mediumDate": "MMM d, y", 33346 "mediumTime": "h:mm:ss a", 33347 "short": "M/d/yy h:mm a", 33348 "shortDate": "M/d/yy", 33349 "shortTime": "h:mm a" 33350 }, 33351 "NUMBER_FORMATS": { 33352 "CURRENCY_SYM": "$", 33353 "DECIMAL_SEP": ".", 33354 "GROUP_SEP": ",", 33355 "PATTERNS": [ 33356 { 33357 "gSize": 3, 33358 "lgSize": 3, 33359 "maxFrac": 3, 33360 "minFrac": 0, 33361 "minInt": 1, 33362 "negPre": "-", 33363 "negSuf": "", 33364 "posPre": "", 33365 "posSuf": "" 33366 }, 33367 { 33368 "gSize": 3, 33369 "lgSize": 3, 33370 "maxFrac": 2, 33371 "minFrac": 2, 33372 "minInt": 1, 33373 "negPre": "-\u00a4", 33374 "negSuf": "", 33375 "posPre": "\u00a4", 33376 "posSuf": "" 33377 } 33378 ] 33379 }, 33380 "id": "en-us", 33381 "localeID": "en_US", 33382 "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;} 33383}); 33384}]); 33385 33386 jqLite(window.document).ready(function() { 33387 angularInit(window.document, bootstrap); 33388 }); 33389 33390})(window); 33391 33392!window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');/** 33393 * WebStorage Service for AngularJS 33394 * 33395 * The webStorage service has both a generic and direct API. The generic 33396 * API will check for client support and preferred order before altering a 33397 * specific storage value, trying to degrade gracefully according to a set 33398 * heuristic. The direct APIs works with either the client's local, session 33399 * or the module's own in-memory storage engines. 33400 * 33401 * The selection heuristics for the generic API is mainly dictated by a set 33402 * order (defaults to `['local', 'session', 'memory']`.) If the client has no
33403 * support for the specified storage engine then the service will try to fall 33404 * back on the next specified engine and so forth. 33405 * 33406 * NOTE: The in-memory storage should really be seen as a last resort since 33407 * all its values will be lost on page reload (somewhat negating the whole 33408 * idea of client web storage!) 33409 * 33410 * If the client does not support local or session web storage the module will 33411 * try to mimic them by setting cookies on the current document. 33412 * 33413 * All errors will be broadcast via the `$rootScope` under a specific name 33414 * (defaults to: `webStorage.notification.error`.) 33415 * 33416 * The service provides the following generic methods: 33417 * 33418 * webStorage 33419 * - isSupported -- boolean flag indicating client support status (local or session storage) 33420 * - add(key, value, all) -- [DEPRECATED: use `set`] add a value to storage under the specific key (storage according to 'order') 33421 * - set(key, value, all) -- add or set a value in storage under the specific key (storage according to 'order') 33422 * - get(key, all) -- return the specified value (storage according to 'order') 33423 * - has(key, all) -- checks if the given key exists (storage according to 'order') 33424 * - key(index, all) -- returns the name of the nth key (storage according to 'order') 33425 * - length(all) -- returns the number of items in the key/value store (storage according to 'order') 33426 * - remove(key, all) -- remove a key/value pair from storage (storage according to 'order') 33427 * - clear(all) -- remove all key/value pairs from storage (storage according to 'order') 33428 * - errorName(str) -- get or set the name of the event that is broadcast over the $rootScope on errors 33429 * - prefix(str) -- get or set the prefix used for keys while operating on storage values 33430 * - order(array) -- get or set the order by which storage models are iterated (defaults to ['local', 'session', 'memory']) 33431 * 33432 * It also provides the following direct APIs: 33433 * 33434 * webStorage.local 33435 * - isSupported -- boolean flag indicating client support status (local storage) 33436 * - add(key, value) -- [DEPRECATED: use `set`] add a value to storage under the specific key (local storage) 33437 * - set(key, value) -- add or update a value in storage under the specific key (local storage) 33438 * - get(key) -- return the specified value (local storage) 33439 * - has(key) -- checks if the given key exists (local storage) 33440 * - key(index) -- return the name of the nth key (local storage) 33441 * - length() -- returns the number of items in storage (local storage) 33442 * - remove(key) -- remove a key/value pair from storage (local storage) 33443 * - clear() -- remove all key/value pairs from storage (local storage) 33444 * - isPolyfilled(remove) -- returns `true` if local storage is polyfilled, if `remove` is true then the polyfill is removed (local storage) 33445 * 33446 * webStorage.session 33447 * - isSupported -- boolean flag indicating client support status (session storage) 33448 * - add(key, value) -- [DEPRECATED: use `set`] add a value to storage under the specific key (session storage) 33449 * - set(key, value) -- add or set a value in storage under the specific key (session storage) 33450 * - get(key) -- return the specified value (session storage) 33451 * - has(key) -- checks if the given key exists (session storage) 33452 * - key(index) -- return the name of the nth key (session storage) 33453 * - length() -- returns the number of items in storage (session storage) 33454 * - remove(key) -- remove a key/value pair from storage (session storage) 33455 * - clear() -- remove all key/value pairs from storage (session storage) 33456 * - isPolyfilled(remove) -- returns `true` if session storage is polyfilled, if `remove` is true then the polyfill is removed (session storage) 33457 * 33458 * webStorage.memory 33459 * - isSupported -- boolean true, the in-memory storage is always supported 33460 * - add(key, value) -- [DEPRECATED: use `set`] add a value to storage under the specific key (in-memory storage) 33461 * - set(key, value) -- add or set a value in storage under the specific key (in-memory storage) 33462 * - get(key) -- return the specified value (in-memory storage) 33463 * - has(key) -- checks if the given key exists (in-memory storage) 33464 * - key(index) -- return the name of the nth key (in-memory storage) 33465 * - length() -- returns the number of items in storage (in-memory storage) 33466 * - remove(key) -- remove a key/value pair from storage (in-memory storage) 33467 * - clear() -- remove all key/value pairs from storage (in-memory storage) 33468 * - isPolyfilled() -- always returns `false` (in-memory storage) 33469 * 33470 * 33471 * Requirements 33472 * This module was originally built for AngularJS v1.0.5. 33473 * 33474 * Usage 33475 * Add `webStorageModule` to your app's dependencies. Then inject `webStorage` 33476 * into any controller that needs to use it, e.g.: 33477 * 33478 * <code> 33479 * var myApp = angular.module('myApp', ['webStorageModule']); 33480 * myApp.controller('myController', function ($scope, webStorage) { ... }); 33481 * </code> 33482 * 33483 * @author Fredric Rylander, https://github.com/fredricrylander/angular-webstorage 33484 * @date 2015-09-01 33485 * @version 0.14.0 33486 * 33487 * @contributor Paulo Cesar (https://github.com/pocesar) 33488 * @contributor David Chang (https://github.com/hasdavidc) 33489 * @contributor David Rodriguez (https://github.com/programmerdave) 33490 * @contributor (https://github.com/jswxwxf) 33491 * @contributor Jose Andres Ramirez (https://github.com/joanrm20) 33492 * @contributor (https://github.com/gorjuce) 33493 * @contributor Sam Blowes (https://github.com/blowsie) 33494 * @contributor Timothee Moulin (https://github.com/timotheemoulin) 33495 * 33496 * The MIT License 33497 * Copyright (c) 2013-2015 Fredric Rylander 33498 * 33499 * Permission is hereby granted, free of charge, to any person obtaining a 33500 * copy of this software and associated documentation files (the "Software"), 33501 * to deal in the Software without restriction, including without limitation 33502 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 33503 * and/or sell copies of the Software, and to permit persons to whom the 33504 * Software is furnished to do so, subject to the following conditions: 33505 * 33506 * The above copyright notice and this permission notice shall be included in 33507 * all copies or substantial portions of the Software. 33508 * 33509 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33510 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33511 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 33512 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33513 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 33514 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 33515 * IN THE SOFTWARE. 33516 */ 33517 33518 /* 33519 * Change Log 33520 * ---------- 33521 * v0.9.0 33522 * - Initial commit. 33523 * 33524 * v0.9.1 33525 * - Bugfix: removed trailing commas that IE choked on, as reported by 33526 * Paulo Cesar (pocesar). 33527 * 33528 * v0.9.2 33529 * - Now using the identity operator instead of equality when comparing 33530 * prefixes while clearing storage. 33531 * - Bugfix: clearSession() is now actually clearing sessionStorage and not 33532 * localStorage, as reported by David Chang (hasdavidc). 33533 * 33534 * v0.9.3 33535 * - Bugfix: now using the `errorName` constant when broadcasting errors 33536 * over the `$rootScope`. 33537 * 33538 * v0.9.4 33539 * - Added strict mode. 33540 * - Bugfix: the module threw access denied exceptions under 'Protected Mode' 33541 * in IE, as reported by (jswxwxf). Fixed by wrapping the sessionStorage and
33542 * localStorage polyfillers in a try/catch-block. 33543 * 33544 * v0.9.5 33545 * - Bugfix: `get()`, `getFromLocal()` and `getFromSession()` will now return 33546 * `null` on errors as expected, reported by David Rodriguez (programmerdave). 33547 * 33548 * v0.10.0 33549 * - Added `allEngines` as an argument to the generic methods (`add`, `get`, 33550 * `remove` and `clear`). This enables the caller to decide if all supported 33551 * storage engines should be queried or only the first supported one. 33552 * The default value for this argument was chosen so that `add()` will only 33553 * use the first supported storage engine, while `get()`, `remove()` and 33554 * `clear()` will query all supported storage engines. The update was 33555 * inspired by David Rodriguezâs (programmerdave) pull request. 33556 * - Added `setStorageOrder()` so that users of the module may alter the order 33557 * by which storage models are iterated. 33558 * - Added `setStoragePrefix()` so that users of the module may alter the 33559 * prefix used when setting, getting or removing values from the store. 33560 * - Added `setErrorName()` so that users of the module may alter the name 33561 * of the event that is broadcast over the `$rootScope` on module errors. 33562 * 33563 * v0.10.1 33564 * - Updated the API documentation with `setErrorName()` and `setStoragePrefix()`. 33565 * - Added the list of contributors to README.md. 33566 * - Added this changelog to README.md. 33567 * 33568 * v0.10.2 33569 * - Refactored `setErrorName()`, `setStorageOrder()` and `setStoragePrefix()` 33570 * from being only setters into also being getters. To reflect this, they 33571 * have been renamed `errorName()`, `order()` and `prefix()` respectively. 33572 * 33573 * v0.10.3 33574 * - Updated the AngularJS version in bower.json so that it now uses semantic 33575 * versioning (semver). 33576 * 33577 * v0.10.4 33578 * - Added the minified version of the source, pull-request by Jose Andres 33579 * Ramirez (joanrm20). 33580 * 33581 * v0.11.0 33582 * - Added the `has` method in order to check if a given key exists in web 33583 * storage or not, as suggested by (gorjuce). 33584 * 33585 * v0.12.0 33586 * - Renamed `add` to `set` in order to mirror the underlying web storage 33587 * interface, as suggested by Sam Blowes (blowsie) and Timothee Moulin 33588 * (timotheemoulin). `add` has been deprecated in version 0.12.0 and 33589 * will be deleted in version 1.0. 33590 * 33591 * v0.13.0 33592 * - Added the `length` method in order to fetch the number of items 33593 * stored in a storage engine. 33594 * - Added the `key` method in order to be able to fetch the name of the 33595 * nth key in a storage engine. 33596 * 33597 * v0.13.1 33598 * - Refactored some strings to var's in order to help minification. 33599 * 33600 * v0.14.0 33601 * - Added routines to check if local and/or session storage has been 33602 * polyfilled or not. It is also possible to use the same routines to 33603 * remove the polyfill functionality. `isPolyfilled(remove)` is now 33604 * defined on `webStorage.local` and `webStorage.session`. 33605 */ 33606 33607/** 33608 * Setup the webStorageModule. 33609 */ 33610var webStorageModule = angular.module('webStorageModule', []); 33611 33612/** 33613 * Module settings. 33614 * 33615 * These are the moduleâs default settings, they may be queried and updated 33616 * via methods with the same name. E.g. the storage model order may be updated 33617 * by calling `webStorage.order(['session', 'local', 'memory'])` and the 33618 * current prefix may be fetched by calling `webStorage.prefix()`. 33619 * 33620 * @see errorName 33621 * @see order 33622 * @see prefix 33623 */ 33624webStorageModule.constant('defaultSettings', { 33625 // Set a prefix that will be used for all storage data (defaults to the empty string.) 33626 // Use prefix() to modify this prefix. 33627 prefix: '', 33628 33629 // The order in which the service selects what storage model to use. Note that 33630 // the module mimics localStorage and sessionStorage by using cookies if one, 33631 // or both, of the web storage models aren't supported. 33632 // Use order() to modify this list. 33633 order: ['local', 'session', 'memory'], 33634 33635 // Name of the event that will be broadcast via the $rootScope on errors. 33636 // Use errorName() to modify this value. 33637 errorName: 'webStorage.notification.error', 33638 33639 // Key used to test the availability of storage engines. 33640 testKey: 'webStorage.test.key' 33641}); 33642 33643/** 33644 * Setup the webStorage service. 33645 */ 33646webStorageModule.factory('webStorage', ['$rootScope', 'defaultSettings', function ($rootScope, defaultSettings) { 33647 'use strict'; 33648 33649 /** 33650 * Constant used for the string `undefined` (in order to help in minification.) 33651 * @private 33652 */ 33653 var STR_UNDEFINED = 'undefined'; 33654 33655 /** 33656 * Warning displayed on `console.warn` when `add()` is used instead of `set()`. 33657 * @private 33658 */ 33659 var addDeprecatedWarning = 'angular-webstorage.js -- `add()` had been deprecated, use `set()` instead'; 33660 33661 /** 33662 * Name of the event that will be broadcast over the $rootScope on errors. 33663 * @see errorName 33664 * @private 33665 */ 33666 var errorName = defaultSettings.errorName; 33667 33668 /** 33669 * Boolean flag indicating client support for local storage. 33670 * @private 33671 */ 33672 var hasLocalStorage = testLocalStorage(); 33673 33674 /** 33675 * Boolean flag indicating client support for session storage. 33676 * @private 33677 */ 33678 var hasSessionStorage = testSessionStorage(); 33679 33680 /** 33681 * Boolean flag indicating if local storage has been polyfilled by using cookies. 33682 * @private 33683 */ 33684 var isLocalStoragePolyfilled = false;
33685 33686 /** 33687 * Boolean flag indicating if session storage has been polyfilled by using cookies. 33688 * @private 33689 */ 33690 var isSessionStoragePolyfilled = false; 33691 33692 /** 33693 * Reference to the order of preference by which storage engines are iterated. 33694 * @see order 33695 * @private 33696 */ 33697 var order = defaultSettings.order; 33698 33699 /** 33700 * Prefix used on key names when setting/getting/deleting values from the web store. 33701 * @see prefix 33702 * @private 33703 */ 33704 var prefix = defaultSettings.prefix; 33705 33706 /** 33707 * In-memory object used as last resort if no web storage engine is supported by the client. 33708 * @private 33709 */ 33710 var ram = {}; 33711 33712 /** 33713 * The webStorage service API. 33714 */ 33715 var webStorage = { 33716 /** Boolean flag indicating that the client has support for some form of web storage or not. */ 33717 isSupported: hasLocalStorage || hasSessionStorage, 33718 33719 /** 33720 * The local storage API. 33721 * The API is the same as the generic API for the webStore service, but will 33722 * only operate directly on the local store. Errors will be broadcast via 33723 * the $rootScope. 33724 */ 33725 local: { 33726 isSupported: hasLocalStorage, 33727 add: addToLocal, // Deprecated: use `set`. 33728 set: setInLocal, 33729 get: getFromLocal, 33730 has: hasInLocal, 33731 key: keyInLocal, 33732 length: lengthInLocal, 33733 remove: removeFromLocal, 33734 clear: clearLocal, 33735 isPolyfilled: isLocalPolyfilled, 33736 }, 33737 33738 /** 33739 * The session storage API. 33740 * The API is the same as the generic API for the webStore service, but will 33741 * only operate directly on the session store. Errors will be broadcast via 33742 * the $rootScope. 33743 */ 33744 session: { 33745 isSupported: hasSessionStorage, 33746 add: addToSession, // Deprecated: use `set`. 33747 set: setInSession, 33748 get: getFromSession, 33749 has: hasInSession, 33750 key: keyInSession, 33751 length: lengthInSession, 33752 remove: removeFromSession, 33753 clear: clearSession, 33754 isPolyfilled: isSessionPolyfilled, 33755 }, 33756 33757 /** 33758 * The in-memory API. 33759 * The API is the same as the generic API for the webStore service, but will 33760 * only operate directly on the in-memory store. Errors will be broadcast via 33761 * the $rootScope. 33762 */ 33763 memory: { 33764 isSupported: true, 33765 add: addToMemory, // Deprecated: use `set`. 33766 set: setInMemory, 33767 get: getFromMemory, 33768 has: hasInMemory, 33769 key: keyInMemory, 33770 length: lengthInMemory, 33771 remove: removeFromMemory, 33772 clear: clearMemory, 33773 isPolyfilled: false 33774 } 33775 }; 33776 33777 /** 33778 * Setter for the key/value web store. 33779 * 33780 * NOTE: This method will use local or session storage depending on the 33781 * client's support as well as the order set in the module constant 33782 * 'order'. If 'allEngines' is true (default is false) then the key/value 33783 * pair will be added to all available storage engines. 33784 * 33785 * @param {string} key Name to store the given value under. 33786 * @param {mixed} value The value to store. 33787 * @param {boolean} allEngines If true, add to all available engines, else 33788 * only add to the first supported storage engine. Default is false. 33789 * @return {boolean} True on success, else false. If 'allEngines' is true 33790 * then success is when the value was added to at least one storage engine. 33791 * @deprecated Since version 0.12.0. Will be deleted in version 1.0. Use `set` instead. 33792 */ 33793 webStorage.add = function (key, value, allEngines) { 33794 console.warn(addDeprecatedWarning); 33795 return webStorage.set(key, value, allEngines); 33796 }; 33797 33798 /** 33799 * Setter for the key/value web store. 33800 * 33801 * NOTE: This method will use local or session storage depending on the 33802 * client's support as well as the order set in the module constant 33803 * 'order'. If 'allEngines' is true (default is false) then the key/value 33804 * pair will be added to all available storage engines. 33805 * 33806 * @param {string} key Name to store the given value under. 33807 * @param {mixed} value The value to store. 33808 * @param {boolean} allEngines If true, add to all available engines, else 33809 * only add to the first supported storage engine. Default is false. 33810 * @return {boolean} True on success, else false. If 'allEngines' is true 33811 * then success is when the value was added to at least one storage engine. 33812 */ 33813 webStorage.set = function (key, value, allEngines) { 33814 allEngines = typeof allEngines !== STR_UNDEFINED ? !!allEngines : false;
33815 var result = false; 33816 var length = order.length; 33817 for (var ith = 0; ith < length; ++ith) { 33818 var engine = webStorage[order[ith]]; 33819 if (engine.isSupported) { 33820 result = engine.set(key, value) || result; 33821 if (!allEngines) { 33822 return result; 33823 } 33824 } 33825 } 33826 return result; 33827 }; 33828 33829 /** 33830 * Getter for the key/value web store. 33831 * 33832 * NOTE: This method will use local or session storage depending on the 33833 * client's support as well as the order set in the module constant 'order'. 33834 * If 'allEngines' is false (default is true) then only the first supported 33835 * storage engine will be queried for the specified key/value, otherwise all 33836 * engines will be queried in turn until a non-null value is returned. 33837 * 33838 * @param {string} key Name of the value to retrieve. 33839 * @param {boolean} allEngines If false only the first supported storage 33840 * engine will be queried for the given key/value pair, otherwise all 33841 * engines will be queried in turn until a non-null value is found. 33842 * Default is true. 33843 * @return {mixed} The value previously added under the specified key, 33844 * else null. 33845 */ 33846 webStorage.get = function (key, allEngines) { 33847 allEngines = typeof allEngines !== STR_UNDEFINED ? !!allEngines : true; 33848 var length = order.length; 33849 for (var ith = 0; ith < length; ++ith) { 33850 var engine = webStorage[order[ith]]; 33851 if (engine.isSupported) { 33852 var value = engine.get(key); 33853 if (!allEngines || value !== null) { 33854 return value; 33855 } 33856 } 33857 } 33858 return null; 33859 }; 33860 33861 /** 33862 * Check if a key exists. 33863 * 33864 * @param {string} key Name of the key to test. 33865 * @param {boolean} allEngines If false only the first supported storage 33866 * engine will be queried for the given key, otherwise all engines will 33867 * be queried in turn until a non-null value is found. Default is true. 33868 * @return {boolean} True if the key exists, else false. 33869 */ 33870 webStorage.has = function (key, allEngines) { 33871 return null !== webStorage.get(key, allEngines); 33872 }; 33873 33874 /** 33875 * Return the name of the nth key in the key/value web store. 33876 * 33877 * @param {number} num An integer representing the number of the key to 33878 * the return the name of. 33879 * @param {boolean} allEngines If false only the first supported storage 33880 * engine will be queried for the given key, otherwise all engines will 33881 * be queried in turn until a non-null value is found. Default is true. 33882 * @return {string|null} The name of the key if available or null otherwise. 33883 */ 33884 webStorage.key = function (index, allEngines) { 33885 allEngines = typeof allEngines !== STR_UNDEFINED ? !!allEngines : true; 33886 var length = order.length; 33887 for (var ith = 0; ith < length; ++ith) { 33888 var engine = webStorage[order[ith]]; 33889 if (engine.isSupported) { 33890 var value = engine.key(index); 33891 if (!allEngines || value !== null) { 33892 return value; 33893 } 33894 } 33895 } 33896 return null; 33897 }; 33898 33899 /** 33900 * Returns an integer representing the number of items stored 33901 * in the key/value web store. 33902 * 33903 * @param {number} num An integer representing the number of the key to 33904 * the return the name of. 33905 * @param {boolean} allEngines If false only the first supported storage 33906 * engine will be queried for itâs length, otherwise all engines will 33907 * be queried in turn until a non-zero value is found. Default is true. 33908 * @return {number} The number of items currently stored in 33909 * the key/value web store. 33910 */ 33911 webStorage.length = function (allEngines) { 33912 allEngines = typeof allEngines !== STR_UNDEFINED ? !!allEngines : true; 33913 var length = order.length; 33914 for (var ith = 0; ith < length; ++ith) { 33915 var engine = webStorage[order[ith]]; 33916 if (engine.isSupported) { 33917 var value = engine.length(); 33918 if (!allEngines || value !== 0) { 33919 return value; 33920 } 33921 } 33922 } 33923 return 0; 33924 }; 33925 33926 /** 33927 * Remove a specified value from the key/value web store. 33928 * 33929 * NOTE: The method will use local or session storage depending on the 33930 * client's support as well as the order set in the module constant 'order'. 33931 * If 'allEngines' is true (the default) then the specified key/value pair 33932 * will be removed from all supported storage engines, otherwise only 33933 * the first supported storage engine will be used for the removal. 33934 * 33935 * @param {string} key Name of the value to remove. 33936 * @param {boolean} allEngines If true, remove from all available engines, 33937 * else only remove from the first supported storage engine. Default is 33938 * true. 33939 * @return {boolean} True on success, else false. If 'allEngines' is true 33940 * then success is when the value was removed from at least one storage 33941 * engine. 33942 */ 33943 webStorage.remove = function (key, allEngines) { 33944 allEngines = typeof allEngines !== STR_UNDEFINED ? !!allEngines : true; 33945 var result = false;
33946 var length = order.length; 33947 for (var ith = 0; ith < length; ++ith) { 33948 var engine = webStorage[order[ith]]; 33949 if (engine.isSupported) { 33950 result = engine.remove(key) || result; 33951 if (!allEngines) { 33952 return result; 33953 } 33954 } 33955 } 33956 return result; 33957 }; 33958 33959 /** 33960 * Remove all values in the key/value web store. 33961 * 33962 * If a prefix has been specified in the module constant 'prefix' then 33963 * only values with that specific prefix will be removed. 33964 * 33965 * NOTE: The method will use local or session storage depending on the 33966 * client's support as well as the order set in the module constant 'order'. 33967 * If 'allEngines' is true (the default) then the all key/value pairs 33968 * will be removed from all supported storage engines, otherwise only 33969 * the first supported storage engine will have its values removed. 33970 * 33971 * @param {boolean} allEngines If true, remove from all available engines, 33972 * else only remove from the first supported storage engine. Default is 33973 * true. 33974 * @return {boolean} True on success, else false. If 'allEngines' is true 33975 * then success is when the all values was removed from at least one 33976 * storage engine. 33977 */ 33978 webStorage.clear = function (allEngines) { 33979 allEngines = typeof allEngines !== STR_UNDEFINED ? !!allEngines : true; 33980 var result = false;
33981 var length = order.length; 33982 for (var ith = 0; ith < length; ++ith) { 33983 var engine = webStorage[order[ith]]; 33984 if (engine.isSupported) { 33985 result = engine.clear() || result; 33986 if (!allEngines) { 33987 return result; 33988 } 33989 } 33990 } 33991 return result; 33992 }; 33993 33994 /** 33995 * Getter/setter for the error name that is used when broadcasting errors 33996 * on the $rootScope. 33997 * 33998 * @param {string} newErrorName (Optional) The new error name. 33999 * @return {mixed} The current (on get) or previous (on set) error name, 34000 * or false on error. 34001 * @see defaultPrefix 34002 */ 34003 webStorage.errorName = function (newErrorName) { 34004 var result = errorName; 34005 if (typeof newErrorName !== STR_UNDEFINED) { 34006 if (typeof newErrorName !== 'string') { 34007 return false; 34008 } 34009 errorName = newErrorName; 34010 } 34011 return result; 34012 }; 34013 34014 /** 34015 * Getter/setter for the order in which the service selects what storage 34016 * model to use. 34017 * 34018 * @param {Array} newOrder (Optional) An array of string names of the order 34019 * to query storage engines. Recognized names are 'local', 'session' 34020 * and 'memory'. All other names are ignored. 34021 * @return {Array} The current (on get) or previous (on set) order as an 34022 * array of strings. 34023 * @see defaultOrder 34024 */ 34025 webStorage.order = function (newOrder) { 34026 var result = angular.copy(order); 34027 if (typeof newOrder !== STR_UNDEFINED) { 34028 order = []; 34029 for (var ith in newOrder) { 34030 if (/^(local|session|memory)$/.test(newOrder[ith])) { 34031 order.push(newOrder[ith]); 34032 } 34033 } 34034 } 34035 return result; 34036 }; 34037 34038 /** 34039 * Getter/setter for the prefix that is used when adding, getting or 34040 * removing data. 34041 * 34042 * @param {string} newPrefix (Optional) The new prefix. 34043 * @return {mixed} The current (on get) or previous (on set) prefix, 34044 * or false on error. 34045 * @see defaultPrefix 34046 */ 34047 webStorage.prefix = function (newPrefix) { 34048 var result = prefix; 34049 if (typeof newPrefix !== STR_UNDEFINED) { 34050 if (typeof newPrefix !== 'string') { 34051 return false; 34052 } 34053 prefix = newPrefix; 34054 } 34055 return result; 34056 }; 34057 34058 /** 34059 * Add the specified key/value pair to the local web store. 34060 * 34061 * NOTE: The web store API only specifies that implementations should be able to 34062 * handle string values, this method will therefore stringify all values into 34063 * JSON strings before storing them. 34064 * 34065 * @param {string} key The name to store the value under. 34066 * @param {mixed} value The value to set (all values are stored as JSON.) 34067 * @return {boolean} True on success, else false. 34068 * @private 34069 * @deprecated Since version 0.12.0. Will be deleted in version 1.0. Use `setInLocal` instead. 34070 */ 34071 function addToLocal(key, value) { 34072 console.warn(addDeprecatedWarning); 34073 return setInLocal(key, value); 34074 } 34075 34076 /** 34077 * Add the specified key/value pair to the session web store. 34078 * 34079 * NOTE: The web store API only specifies that implementations should be able to 34080 * handle string values, this method will therefore stringify all values into 34081 * JSON strings before storing them. 34082 * 34083 * @param {string} key The name to store the value under. 34084 * @param {mixed} value The value to set (all values are stored as JSON.) 34085 * @return {boolean} True on success, else false. 34086 * @private 34087 * @deprecated Since version 0.12.0. Will be deleted in version 1.0. Use `setInSession` instead. 34088 */ 34089 function addToSession(key, value) { 34090 console.warn(addDeprecatedWarning); 34091 return setInSession(key, value); 34092 } 34093 34094 /** 34095 * Add the specified key/value pair to the in-memory store. 34096 * 34097 * NOTE: The in-memory storage does not use prefixes. 34098 * 34099 * @param {string} key The name to store the value under. 34100 * @param {mixed} value The value to set. 34101 * @return {boolean} True on success, else false. 34102 * @private 34103 * @deprecated Since version 0.12.0. Will be deleted in version 1.0. Use `setInMemory` instead. 34104 */ 34105 function addToMemory(key, value) { 34106 console.warn(addDeprecatedWarning); 34107 return setInMemory(key, value); 34108 } 34109 34110 /** 34111 * Add or update the specified key/value pair in the local web store. 34112 * 34113 * NOTE: The web store API only specifies that implementations should be able to 34114 * handle string values, this method will therefore stringify all values into 34115 * JSON strings before storing them. 34116 * 34117 * @param {string} key The name to store the value under. 34118 * @param {mixed} value The value to set (all values are stored as JSON.) 34119 * @return {boolean} True on success, else false. 34120 * @private 34121 */ 34122 function setInLocal(key, value) { 34123 if (hasLocalStorage) { 34124 try { 34125 localStorage.setItem(prefix + key, JSON.stringify(value)); 34126 } catch (e) { 34127 return croak(e); 34128 } 34129 return true; 34130 } 34131 return false;
34132 } 34133 34134 /** 34135 * Add or update the specified key/value pair in the session web store. 34136 * 34137 * NOTE: The web store API only specifies that implementations should be able to 34138 * handle string values, this method will therefore stringify all values into 34139 * JSON strings before storing them. 34140 * 34141 * @param {string} key The name to store the value under. 34142 * @param {mixed} value The value to set (all values are stored as JSON.) 34143 * @return {boolean} True on success, else false. 34144 * @private 34145 */ 34146 function setInSession(key, value) { 34147 if (hasSessionStorage) { 34148 try { 34149 sessionStorage.setItem(prefix + key, JSON.stringify(value)); 34150 } catch (e) { 34151 return croak(e); 34152 } 34153 return true; 34154 } 34155 return false; 34156 } 34157 34158 /** 34159 * Add or update the specified key/value pair in the in-memory store. 34160 * 34161 * NOTE: The in-memory storage does not use prefixes. 34162 * 34163 * @param {string} key The name to store the value under. 34164 * @param {mixed} value The value to set. 34165 * @return {boolean} True on success, else false. 34166 * @private 34167 */ 34168 function setInMemory(key, value) { 34169 ram[key] = value; 34170 return true; 34171 } 34172 34173 /** 34174 * Get the specified value from the local web store. 34175 * 34176 * NOTE: Since all values are stored as JSON strings, this method will parse the fetched 34177 * JSON string and return the resulting object/value. 34178 * 34179 * @param {string} key The name of the value. 34180 * @return {mixed} The value previously added under the specified key, else null. 34181 * @private 34182 */ 34183 function getFromLocal(key) { 34184 if (hasLocalStorage) { 34185 try { 34186 var value = localStorage.getItem(prefix + key); 34187 return value && JSON.parse(value); 34188 } catch (e) { 34189 croak(e); 34190 return null; 34191 } 34192 } 34193 return null; 34194 } 34195 34196 /** 34197 * Get the specified value from the session web store. 34198 * 34199 * NOTE: Since all values are stored as JSON strings, this method will parse the fetched 34200 * JSON string and return the resulting object/value. 34201 * 34202 * @param {string} key The name of the value. 34203 * @return {mixed} The value previously added under the specified key, else null. 34204 * @private 34205 */ 34206 function getFromSession(key) { 34207 if (hasSessionStorage) { 34208 try { 34209 var value = sessionStorage.getItem(prefix + key); 34210 return value && JSON.parse(value); 34211 } catch (e) { 34212 croak(e); 34213 return null; 34214 } 34215 } 34216 return null; 34217 } 34218 34219 /** 34220 * Get the specified value from the in-memory store. 34221 * 34222 * NOTE: The in-memory storage does not use prefixes. 34223 * 34224 * @param {string} key The name of the value. 34225 * @return {mixed} The value previously added under the specified key, else null. 34226 * @private 34227 */ 34228 function getFromMemory(key) { 34229 return key in ram ? ram[key] : null; 34230 } 34231 34232 /** 34233 * Check if the given key exists in the local web store. 34234 * 34235 * @param {string} key The name of the value. 34236 * @return {boolean} True if the key exists, else false. 34237 * @private 34238 */ 34239 function hasInLocal(key) { 34240 return null !== getFromLocal(key); 34241 } 34242 34243 /** 34244 * Check if the given key exists in the session web store. 34245 * 34246 * @param {string} key The name of the value. 34247 * @return {boolean} True if the key exists, else false. 34248 * @private 34249 */ 34250 function hasInSession(key) { 34251 return null !== getFromSession(key); 34252 } 34253 34254 /** 34255 * Check if the given key exists in the in-memory store. 34256 * 34257 * @param {string} key The name of the value. 34258 * @return {boolean} True if the key exists, else false. 34259 * @private 34260 */ 34261 function hasInMemory(key) { 34262 return null !== getFromMemory(key); 34263 } 34264 34265 /** 34266 * Return the name of the nth key in the local web store. 34267 * 34268 * @param {number} num An integer representing the number 34269 * of the key to the return the name of. 34270 * @return {string|null} The name of the key if available 34271 * or null otherwise. 34272 */ 34273 function keyInLocal(num) { 34274 if (hasLocalStorage) { 34275 return localStorage.key(num); 34276 } 34277 return null; 34278 } 34279 34280 /** 34281 * Return the name of the nth key in the session web store. 34282 * 34283 * @param {number} num An integer representing the number 34284 * of the key to the return the name of. 34285 * @return {string|null} The name of the key if available 34286 * or null otherwise. 34287 */ 34288 function keyInSession(num) { 34289 if (hasSessionStorage) { 34290 return sessionStorage.key(num); 34291 } 34292 return null; 34293 } 34294 34295 /** 34296 * Return the name of the nth key in the memory store. 34297 * 34298 * @param {number} index An integer representing the number 34299 * of the key to the return the name of. 34300 * @return {string|null} The name of the key if available 34301 * or null otherwise. 34302 */ 34303 function keyInMemory(index) { 34304 var count = 0; 34305 for (var key in ram) { 34306 if (ram.hasOwnProperty(key)) { 34307 if (index === count) { 34308 return key; 34309 } 34310 count += 1; 34311 if (count > index) { 34312 return null; 34313 } 34314 } 34315 } 34316 return null; 34317 } 34318 34319 /** 34320 * Returns an integer representing the number of items stored 34321 * in the local store. 34322 * 34323 * @return {number} The number of items currently stored in 34324 * the local store. 34325 */ 34326 function lengthInLocal() { 34327 if (hasLocalStorage) { 34328 return localStorage.length; 34329 } 34330 return 0; 34331 } 34332 34333 /** 34334 * Returns an integer representing the number of items stored 34335 * in the session store. 34336 * 34337 * @return {number} The number of items currently stored in 34338 * the session store. 34339 */ 34340 function lengthInSession() { 34341 if (hasSessionStorage) { 34342 return sessionStorage.length; 34343 } 34344 return 0; 34345 } 34346 34347 /** 34348 * Returns an integer representing the number of items stored 34349 * in the in-memory store. 34350 * 34351 * @return {number} The number of items currently stored in 34352 * the in-memory store. 34353 */ 34354 function lengthInMemory() { 34355 if (Object.keys) { 34356 return Object.keys(ram).length; 34357 } 34358 var count = 0; 34359 for (var key in ram) { 34360 if (ram.hasOwnProperty(key)) { 34361 count += 1; 34362 } 34363 } 34364 return count; 34365 } 34366 34367 /** 34368 * Remove the specified key/value pair from the local store. 34369 * 34370 * @param {string} key The name of the value to remove. 34371 * @return {boolean} True on success, else false. 34372 * @private 34373 */ 34374 function removeFromLocal(key) { 34375 if (hasLocalStorage) { 34376 try { 34377 localStorage.removeItem(prefix + key); 34378 } catch (e) { 34379 return croak(e); 34380 } 34381 return true; 34382 } 34383 return false;
34384 } 34385 34386 /** 34387 * Remove the specified key/value pair from the session store. 34388 * 34389 * @param {string} key The name of the value to remove. 34390 * @return {boolean} True on success, else false. 34391 * @private 34392 */ 34393 function removeFromSession(key) { 34394 if (hasSessionStorage) { 34395 try { 34396 sessionStorage.removeItem(prefix + key); 34397 } catch (e) { 34398 return croak(e); 34399 } 34400 return true; 34401 } 34402 return false; 34403 } 34404 34405 /** 34406 * Remove the specified key/value pair from the in-memory store. 34407 * 34408 * NOTE: The in-memory storage does not use prefixes. 34409 * 34410 * @param {string} key The name of the value to remove. 34411 * @return {boolean} True on success, else false. 34412 * @private 34413 */ 34414 function removeFromMemory(key) { 34415 delete ram[key]; 34416 return true; 34417 } 34418 34419 /** 34420 * Clear all key/value pairs form the local store. 34421 * 34422 * NOTE: If a prefix has been specified in the module constant 'prefix' then only 34423 * values with that specific prefix will be removed. 34424 * 34425 * @return {boolean} True on success, else false. 34426 * @private 34427 */ 34428 function clearLocal() { 34429 if (!hasLocalStorage) return false; 34430 if (!!prefix) { 34431 var prefixLength = prefix.length; 34432 try { 34433 for (var key in localStorage) { 34434 if (key.substr(0, prefixLength) === prefix) { 34435 localStorage.removeItem(key); 34436 } 34437 } 34438 } catch (e) { 34439 return croak(e); 34440 } 34441 return true; 34442 } 34443 34444 try { 34445 localStorage.clear(); 34446 } catch (e) { 34447 return croak(e); 34448 } 34449 34450 return true; 34451 } 34452 34453 /** 34454 * Clear all key/value pairs form the session store. 34455 * 34456 * NOTE: If a prefix has been specified in the module constant 'prefix' then only 34457 * values with that specific prefix will be removed. 34458 * 34459 * @return {boolean} True on success, else false. 34460 * @private 34461 */ 34462 function clearSession() { 34463 if (!hasSessionStorage) return false; 34464 if (!!prefix) { 34465 var prefixLength = prefix.length; 34466 try { 34467 for (var key in sessionStorage) { 34468 if (key.substr(0, prefixLength) === prefix) { 34469 sessionStorage.removeItem(key); 34470 } 34471 } 34472 } catch (e) { 34473 return croak(e); 34474 } 34475 return true; 34476 } 34477 34478 try { 34479 sessionStorage.clear(); 34480 } catch (e) { 34481 return croak(e); 34482 } 34483 34484 return true; 34485 } 34486 34487 /** 34488 * Clear all key/value pairs form the in-memory store. 34489 * 34490 * NOTE: The in-memory storage does not use prefixes. 34491 * 34492 * @return {boolean} True on success, else false. 34493 * @private 34494 */ 34495 function clearMemory() { 34496 ram = {}; 34497 return true; 34498 } 34499 34500 /** 34501 * Test the client's support for storing values in the local store. 34502 * 34503 * @return {boolean} True if the client has support for the local store, else false. 34504 * @private 34505 */ 34506 function testLocalStorage() { 34507 polyfillLocalStorage(); 34508 try { 34509 localStorage.setItem(prefix + defaultSettings.testKey, defaultSettings.testKey); 34510 localStorage.removeItem(prefix + defaultSettings.testKey); 34511 return true; 34512 } catch (e) { 34513 return false; 34514 } 34515 } 34516 34517 /** 34518 * Test the client's support for storing values in the session store. 34519 * 34520 * @return {boolean} True if the client has support for the session store, else false. 34521 * @private 34522 */ 34523 function testSessionStorage() { 34524 polyfillSessionStorage(); 34525 try { 34526 sessionStorage.setItem(prefix + defaultSettings.testKey, defaultSettings.testKey); 34527 sessionStorage.removeItem(prefix + defaultSettings.testKey); 34528 return true; 34529 } catch (e) { 34530 return false; 34531 } 34532 } 34533 34534 /** 34535 * Helper method, broadcasts an error notification on exceptions. 34536 * 34537 * @return {boolean} Always returns false. 34538 * @private 34539 */ 34540 function croak(error) { 34541 $rootScope.$broadcast(errorName, error.title + ': ' + error.message); 34542 return false; 34543 } 34544 34545 /** 34546 * Polyfilling the localStorage API by setting cookies on the document. 34547 * @private 34548 */ 34549 function polyfillLocalStorage() { 34550 try { 34551 /* jshint -W001 */// 'hasOwnProperty' is a really bad name. 34552 /* jshint -W014 */// Bad line break before +. 34553 34554 // Support for localStorage, compatible with old browsers, like Internet 34555 // Explorer < 8 (tested and working even in Internet Explorer 6). 34556 // Source From: https://developer.mozilla.org/en-US/docs/DOM/Storage 34557 if (!window.localStorage) { 34558 window.localStorage = { 34559 getItem : function(sKey) { 34560 if (!sKey || !this.hasOwnProperty(sKey)) { 34561 return null; 34562 } 34563 return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" 34564 + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") 34565 + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1")); 34566 }, 34567 key : function(nKeyId) { 34568 return unescape(document.cookie.replace(/\s*\=(?:.(?!;))*$/, "") 34569 .split(/\s*\=(?:[^;](?!;))*[^;]?;\s*/)[nKeyId]); 34570 }, 34571 setItem : function(sKey, sValue) { 34572 if (!sKey) { 34573 return; 34574 } 34575 document.cookie = escape(sKey) + "=" + escape(sValue) 34576 + "; expires=Tue, 19 Jan 2038 03:14:07 GMT; path=/"; 34577 this.length = document.cookie.match(/\=/g).length; 34578 }, 34579 length : 0, 34580 removeItem : function(sKey) { 34581 if (!sKey || !this.hasOwnProperty(sKey)) { 34582 return; 34583 } 34584 document.cookie = escape(sKey) 34585 + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; 34586 this.length--; 34587 }, 34588 hasOwnProperty : function(sKey) { 34589 return (new RegExp("(?:^|;\\s*)" 34590 + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")) 34591 .test(document.cookie); 34592 } 34593 }; 34594 window.localStorage.length = (document.cookie.match(/\=/g) || window.localStorage).length; 34595 isLocalStoragePolyfilled = true; 34596 } 34597 } catch (e) {
34598 // Protected Mode on IE? There's really nothing to do at this stage. 34599 } 34600 } 34601 34602 /** 34603 * Polyfilling the sessionStorage API by setting cookies on the document. 34604 * @private 34605 */ 34606 function polyfillSessionStorage() { 34607 try { 34608 /* jshint -W001 */// 'hasOwnProperty' is a really bad name. 34609 /* jshint -W014 */// Bad line break before +. 34610 34611 // Support for sessionStorage, compatible with old browsers, like Internet 34612 // Explorer < 8 (tested and working even in Internet Explorer 6). 34613 // Source From: https://developer.mozilla.org/en-US/docs/DOM/Storage 34614 if (!window.sessionStorage) { 34615 window.sessionStorage = { 34616 getItem : function(sKey) { 34617 if (!sKey || !this.hasOwnProperty(sKey)) { 34618 return null; 34619 } 34620 return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" 34621 + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") 34622 + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1")); 34623 }, 34624 key : function(nKeyId) { 34625 return unescape(document.cookie.replace(/\s*\=(?:.(?!;))*$/, "") 34626 .split(/\s*\=(?:[^;](?!;))*[^;]?;\s*/)[nKeyId]); 34627 }, 34628 setItem : function(sKey, sValue) { 34629 if (!sKey) { 34630 return; 34631 } 34632 document.cookie = escape(sKey) + "=" + escape(sValue) + "; path=/"; 34633 this.length = document.cookie.match(/\=/g).length; 34634 }, 34635 length : 0, 34636 removeItem : function(sKey) { 34637 if (!sKey || !this.hasOwnProperty(sKey)) { 34638 return; 34639 } 34640 document.cookie = escape(sKey) 34641 + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/"; 34642 this.length--; 34643 }, 34644 hasOwnProperty : function(sKey) { 34645 return (new RegExp("(?:^|;\\s*)" 34646 + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")) 34647 .test(document.cookie); 34648 } 34649 }; 34650 window.sessionStorage.length = (document.cookie.match(/\=/g) || window.sessionStorage).length; 34651 isSessionStoragePolyfilled = true; 34652 } 34653 34654 } catch (e) { 34655 // Protected Mode on IE? There's really nothing to do at this stage. 34656 } 34657 } 34658 34659 /** 34660 * Returns the polyfill status of local storage: `true` if local storage has been 34661 * polyfilled, else `false`. 34662 * 34663 * @param {boolean} removePolyfill If `removePolyfill` is `true` and local storage 34664 * is currently polyfilled then `window.localStorage` will be set to `null`. 34665 * @return {boolean} `true` if local storage is polyfilled by setting cookies on 34666 * the document, else `false`. 34667 */ 34668 function isLocalPolyfilled(removePolyfill) { 34669 var oldValue = isLocalStoragePolyfilled; 34670 if (removePolyfill === true && isLocalStoragePolyfilled) { 34671 window.localStorage = null; 34672 isLocalStoragePolyfilled = false; 34673 } 34674 return oldValue; 34675 } 34676 34677 /** 34678 * Returns the polyfill status of session storage: `true` if session storage has been 34679 * polyfilled, else `false`. 34680 * 34681 * @param {boolean} removePolyfill If `removePolyfill` is `true` and session storage 34682 * is currently polyfilled then `window.sessionStorage` will be set to `null`. 34683 * @return {boolean} `true` if session storage is polyfilled by setting cookies on 34684 * the document, else `false`. 34685 */ 34686 function isSessionPolyfilled(removePolyfill) { 34687 var oldValue = isSessionStoragePolyfilled; 34688 if (removePolyfill === true && isSessionStoragePolyfilled) { 34689 window.sessionStorage = null; 34690 isLocalStoragePolyfilled = false; 34691 } 34692 return oldValue; 34693 } 34694 34695 return webStorage; 34696}]); 34697(function (angular) { 34698 'use strict'; 34699 34700 angular.module('pickadate', []); 34701 34702 angular.module('pickadate').provider('pickADate', pickADateProvider); 34703 34704 angular.module('pickadate').provider('pickATime', pickATimeProvider); 34705 34706 angular.module('pickadate').directive('pickADate', ['$parse', 'pickADate', function ($parse, pickADate) { 34707 return { 34708 restrict: 'A', 34709 require: '?ngModel', 34710 link: function (scope, element, attrs, ngModel) { 34711 var hasOnOpenRun = false; 34712 var model = { 34713 pickADate: $parse(attrs.pickADate), 34714 minDate: $parse(attrs.minDate), 34715 maxDate: $parse(attrs.maxDate), 34716 pickADateOptions: $parse(attrs.pickADateOptions) 34717 }; 34718 34719 var defaultOptions = pickADate.getOptions() || {}; 34720 var userOptions = model.pickADateOptions(scope) || {}; 34721 var options = angular.extend({}, defaultOptions, userOptions); 34722 34723 options.onSet = function (e) { 34724 var that = this, 34725 args = arguments,
34726 select = element.pickadate('picker').get('select'); // selected date 34727 34728 scope.$evalAsync(function () { 34729 if (e.hasOwnProperty('clear')) { 34730 model.pickADate.assign(scope, null); 34731 return; 34732 } 34733 if (select) { 34734 var date = model.pickADate(scope); 34735 if (!date) { 34736 date = new Date(0); 34737 model.pickADate.assign(scope, date); 34738 } 34739 date.setFullYear(select.obj.getFullYear(), select.obj.getMonth(), select.obj.getDate()); 34740 } else { 34741 model.pickADate.assign(scope, select); 34742 } 34743 if (userOptions && userOptions.onSet) { 34744 userOptions.onSet.apply(that, args); 34745 } 34746 if (defaultOptions && defaultOptions.onSet) { 34747 defaultOptions.onSet.apply(that, args); 34748 } 34749 }); 34750 }; 34751 34752 options.onOpen = function (e) { 34753 if (ngModel) { 34754 if (!hasOnOpenRun) { 34755 hasOnOpenRun = true; 34756 scope.$apply(function () { 34757 ngModel.$setUntouched(); 34758 }); 34759 } 34760 } 34761 if (userOptions && userOptions.onOpen) { 34762 userOptions.onOpen.apply(this, arguments); 34763 } 34764 if (defaultOptions && defaultOptions.onOpen) { 34765 defaultOptions.onOpen.apply(this, arguments); 34766 } 34767 }; 34768 34769 options.onClose = function () { 34770 if (ngModel) { 34771 scope.$applyAsync(function () { 34772 ngModel.$setTouched(); 34773 }); 34774 } 34775 if (userOptions && userOptions.onClose) { 34776 userOptions.onClose.apply(this, arguments); 34777 } 34778 if (defaultOptions && defaultOptions.onClose) { 34779 defaultOptions.onClose.apply(this, arguments); 34780 } 34781 element.blur(); 34782 }; 34783 34784 element.pickadate(options); 34785 function updateValue(newValue) { 34786 if (newValue) { 34787 var date = (newValue instanceof Date) ? newValue : new Date(newValue); 34788 element.pickadate('picker').set('select', date.getTime()); 34789 model.pickADate.assign(scope, date); 34790 } else { 34791 element.pickadate('picker').clear(); 34792 model.pickADate.assign(scope, null); 34793 } 34794 } 34795 34796 updateValue(model.pickADate(scope)); 34797 var minDate = model.minDate(scope); 34798 var maxDate = model.maxDate(scope); 34799 element.pickadate('picker').set('min', minDate ? minDate : false); 34800 element.pickadate('picker').set('max', maxDate ? maxDate : false); 34801 34802 scope.$watchGroup([attrs.pickADate, attrs.minDate, attrs.maxDate], function(newValues, oldValues) { 34803 var newValue = newValues[0], newMin = newValues[1], newMax = newValues[2], 34804 oldValue = oldValues[0], oldMin = oldValues[1], oldMax = oldValues[2]; 34805 34806 if (newMin !== oldMin) { 34807 element.pickadate('picker').set('min', newValues[1] ? newValues[1] : false); 34808 } 34809 if (newMax !== oldMax) { 34810 element.pickadate('picker').set('max', newValues[2] ? newValues[2] : false); 34811 } 34812 if (newValue !== oldValue) { 34813 updateValue(newValues[0]); 34814 } 34815 }, true); 34816 34817 if (ngModel) { 34818 ngModel.$setPristine(); 34819 } 34820 34821 function updateOptions(newValue) { 34822 if (typeof newValue === "object") { 34823 element.pickadate('picker').set(newValue) 34824 } 34825 } 34826 34827 scope.$watch(attrs.pickADateOptions, function (newValue, oldValue) { 34828 if (newValue === oldValue) { 34829 return; 34830 } 34831 updateOptions(newValue) 34832 }, true); 34833 } 34834 }; 34835 }]); 34836 34837 angular.module('pickadate').directive('pickATime', ['$parse', 'pickATime', function ($parse, pickATime) { 34838 return { 34839 restrict: 'A', 34840 require: '?ngModel', 34841 link: function (scope, element, attrs, ngModel) { 34842 var hasOnOpenRun = false; 34843 var model = { 34844 pickATime: $parse(attrs.pickATime), 34845 pickATimeOptions: $parse(attrs.pickATimeOptions) 34846 }; 34847 34848 var defaultOptions = pickATime.getOptions() || {}; 34849 var userOptions = model.pickATimeOptions(scope) || {}; 34850 var options = angular.extend({}, defaultOptions, userOptions); 34851 34852 options.onSet = function (e) { 34853 var that = this, 34854 args = arguments, 34855 select = element.pickatime('picker').get('select'); // selected date 34856 34857 scope.$evalAsync(function () { 34858 if (e.hasOwnProperty('clear')) { 34859 model.pickATime.assign(scope, null); 34860 return; 34861 } 34862 if (select) { 34863 var date = model.pickATime(scope); 34864 if (!date) { 34865 date = new Date(0); 34866 model.pickATime.assign(scope, date); 34867 } 34868 date.setHours(select.hour); 34869 date.setMinutes(select.mins); 34870 date.setSeconds(0); 34871 date.setMilliseconds(0); 34872 } else { 34873 model.pickATime.assign(scope, select); 34874 } 34875 if (userOptions && userOptions.onSet) { 34876 userOptions.onSet.apply(that, args); 34877 } 34878 if (defaultOptions && defaultOptions.onSet) { 34879 defaultOptions.onSet.apply(that, args); 34880 } 34881 }); 34882 }; 34883 34884 options.onOpen = function () { 34885 if (ngModel) { 34886 if (!hasOnOpenRun) { 34887 hasOnOpenRun = true; 34888 scope.$apply(function () { 34889 ngModel.$setUntouched(); 34890 }); 34891 } 34892 } 34893 if (userOptions && userOptions.onOpen) { 34894 userOptions.onOpen.apply(this, arguments); 34895 } 34896 if (defaultOptions && defaultOptions.onOpen) { 34897 defaultOptions.onOpen.apply(this, arguments); 34898 } 34899 }; 34900 34901 options.onClose = function () { 34902 if (ngModel) { 34903 scope.$applyAsync(function () { 34904 ngModel.$setTouched(); 34905 }); 34906 } 34907 if (userOptions && userOptions.onClose) { 34908 userOptions.onClose.apply(this, arguments); 34909 } 34910 if (defaultOptions && defaultOptions.onClose) { 34911 defaultOptions.onClose.apply(this, arguments); 34912 } 34913 element.blur(); 34914 }; 34915 34916 element.pickatime(options); 34917 function updateValue(newValue) { 34918 if (newValue) { 34919 var date = (newValue instanceof Date) ? newValue : new Date(newValue); 34920 var totalMins = date.getHours() * 60 + date.getMinutes(); 34921 element.pickatime('picker').set('select', totalMins); 34922 model.pickATime.assign(scope, date); 34923 } else { 34924 element.pickatime('picker').clear(); 34925 model.pickATime.assign(scope, null); 34926 } 34927 } 34928
34929 updateValue(model.pickATime(scope)); 34930 scope.$watch(attrs.pickATime, function (newValue, oldValue) { 34931 if (newValue === oldValue) { 34932 return; 34933 } 34934 updateValue(newValue); 34935 }, true); 34936 34937 function updateOptions(newValue) { 34938 if (typeof newValue === "object") { 34939 element.pickatime('picker').set(newValue) 34940 } 34941 } 34942 34943 scope.$watch(attrs.pickATimeOptions, function (newValue, oldValue) { 34944 if (newValue === oldValue) { 34945 return; 34946 } 34947 updateOptions(newValue) 34948 }, true); 34949 34950 34951 if (ngModel) { 34952 ngModel.$setPristine(); 34953 } 34954 } 34955 }; 34956 }]); 34957 34958 function pickADateProvider() { 34959 var config; 34960 this.setOptions = function (options) { 34961 if (config) { 34962 throw new Error("Already configured."); 34963 } 34964 if (!(options instanceof Object)) { 34965 throw new TypeError("Invalid argument: `config` must be an `Object`."); 34966 } 34967 config = angular.extend({}, options); 34968 return config; 34969 }; 34970 this.$get = function () { 34971 var PickADate = function () { 34972 function PickADate() {} 34973 34974 Object.defineProperties(PickADate.prototype, { 34975 getOptions: { 34976 value: function getOptions() { 34977 return angular.copy(config); 34978 }, 34979 writable: true, 34980 enumerable: true, 34981 configurable: true 34982 }, 34983 }); 34984 return PickADate; 34985 }(); 34986 return new PickADate(); 34987 }; 34988 this.$get.$inject = []; 34989 } 34990 34991 function pickATimeProvider() { 34992 var config; 34993 this.setOptions = function (options) { 34994 if (config) { 34995 throw new Error("Already configured."); 34996 } 34997 if (!(options instanceof Object)) { 34998 throw new TypeError("Invalid argument: `config` must be an `Object`."); 34999 } 35000 config = angular.extend({}, options); 35001 return config; 35002 }; 35003 this.$get = function () { 35004 var PickATime = function () { 35005 function PickATime() {} 35006 35007 Object.defineProperties(PickATime.prototype, { 35008 getOptions: { 35009 value: function getOptions() { 35010 return angular.copy(config); 35011 }, 35012 writable: true, 35013 enumerable: true, 35014 configurable: true 35015 }, 35016 }); 35017 return PickATime; 35018 }(); 35019 return new PickATime(); 35020 }; 35021 this.$get.$inject = []; 35022 } 35023})(angular); 35024/* ng-infinite-scroll - v1.0.0 - 2013-02-23 */ 35025var mod; 35026 35027mod = angular.module('infinite-scroll', []); 35028 35029mod.directive('infiniteScroll', [ 35030 '$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) { 35031 return { 35032 link: function(scope, elem, attrs) { 35033 var checkWhenEnabled, handler, scrollDistance, scrollEnabled; 35034 $window = angular.element($window); 35035 scrollDistance = 0; 35036 if (attrs.infiniteScrollDistance != null) { 35037 scope.$watch(attrs.infiniteScrollDistance, function(value) { 35038 return scrollDistance = parseInt(value, 10); 35039 }); 35040 } 35041 scrollEnabled = true; 35042 checkWhenEnabled = false; 35043 if (attrs.infiniteScrollDisabled != null) { 35044 scope.$watch(attrs.infiniteScrollDisabled, function(value) { 35045 scrollEnabled = !value; 35046 if (scrollEnabled && checkWhenEnabled) { 35047 checkWhenEnabled = false; 35048 return handler(); 35049 } 35050 }); 35051 } 35052 handler = function() { 35053 var elementBottom, remaining, shouldScroll, windowBottom; 35054 windowBottom = $window.height() + $window.scrollTop(); 35055 elementBottom = elem.offset().top + elem.height(); 35056 remaining = elementBottom - windowBottom; 35057 shouldScroll = remaining <= $window.height() * scrollDistance; 35058 if (shouldScroll && scrollEnabled) { 35059 if ($rootScope.$$phase) { 35060 return scope.$eval(attrs.infiniteScroll); 35061 } else { 35062 return scope.$apply(attrs.infiniteScroll); 35063 } 35064 } else if (shouldScroll) { 35065 return checkWhenEnabled = true; 35066 } 35067 }; 35068 $window.on('scroll', handler); 35069 scope.$on('$destroy', function() { 35070 return $window.off('scroll', handler); 35071 }); 35072 return $timeout((function() { 35073 if (attrs.infiniteScrollImmediateCheck) { 35074 if (scope.$eval(attrs.infiniteScrollImmediateCheck)) { 35075 return handler(); 35076 } 35077 } else { 35078 return handler(); 35079 } 35080 }), 0); 35081 } 35082 }; 35083 } 35084]); 35085/* global angular: false, jQuery: false, console: false */ 35086(function(angular, $) { 35087 35088 var aafapp = angular.module( 35089 'aafapp', 35090 ['webStorageModule', 'pickadate', 'infinite-scroll'] 35091 ); 35092 35093 angular.module('infinite-scroll').value('THROTTLE_MILLISECONDS', 200); 35094 35095 /****************************************************************************** 35096 * Configuration result getter 35097 */ 35098 var aafcfgservice = {}; 35099 aafapp.factory('aafcfg', 35100 [ 35101 function() { 35102 // console.log('Enter AAF Cfg Service'); 35103 aafcfgservice.mode = $('.suchoptionen').data('mode'); 35104 var now = new Date(); 35105 switch(aafcfgservice.mode) { 35106 case 'search': 35107 aafcfgservice.init_start = null; 35108 aafcfgservice.init_end = null; 35109 aafcfgservice.init_sort = 'relevance'; 35110 aafcfgservice.init_order = 'normal'; 35111 break; 35112 case 'future': 35113 aafcfgservice.init_start = now; 35114 aafcfgservice.init_end = null; 35115 aafcfgservice.init_sort = 'date'; 35116 aafcfgservice.init_order = 'normal'; 35117 break; 35118 case 'past': 35119 aafcfgservice.init_start = null; 35120 var future = new Date(); 35121 future.setYear(now.getFullYear() + 2); 35122 aafcfgservice.init_end = future; 35123 aafcfgservice.init_sort = 'date'; 35124 aafcfgservice.init_order = 'reversed'; 35125 break; 35126 case 'archive': 35127 aafcfgservice.init_start = null; 35128 aafcfgservice.init_end = now; 35129 aafcfgservice.init_sort = 'date'; 35130 aafcfgservice.init_order = 'reversed'; 35131 break; 35132 } 35133 return aafcfgservice; 35134 }]); 35135 /****************************************************************************** 35136 * SERVICE result getter 35137 */ 35138 aafapp.factory('result', 35139 [ 35140 '$http', 35141 'webStorage', 35142 '$timeout', 35143 function($http, webStorage, $timeout) { 35144 // console.log('Enter Result Service'); 35145 var service = {}; 35146 service.data = { 35147 records: {}, 35148 uids_in_order: [], 35149 }; 35150 35151 service.settings = { 35152 initialized: false, 35153 batch_size: 7, 35154 batch_delta: 7 35155 }; 35156 service.status = { 35157 loading_results: false 35158 }; 35159 service.load = function(querydata, missing_uids, notify_cb) { 35160 // console.log('Loading query from backend'); 35161 // query server, ask to send only records we dont have already 35162 if (service.status.loading_results) { 35163 // console.log('Already loading: ' + service.status.loading_results); 35164 // retry in short time 35165 if (!service.data.error) { 35166 // console.log('-> retry'); 35167 $timeout( 35168 function() { 35169 service.load(querydata, missing_uids, notify_cb); 35170 }, 500 35171 ); 35172 } else { 35173 // console.log('-> do not retry due to error'); 35174 } 35175 return; 35176 } 35177 var serverquery = {}; 35178 if (missing_uids) { 35179 service.status.loading_results = 'missing'; 35180 serverquery.missing = missing_uids; 35181 } 35182 if (querydata) { 35183 service.data.uids_in_order = []; 35184 service.status.loading_results = 'query'; 35185 serverquery.loaded = []; 35186 serverquery.query = JSON.parse(JSON.stringify(querydata)); 35187 serverquery.batch = service.settings.batch_size + 35188 service.settings.batch_delta; 35189 angular.forEach(service.data.records, function(record) { 35190 serverquery.loaded.push(record.uid); 35191 }); 35192 } 35193 $http.post(service.settings.url, serverquery).success( 35194 function(resp) { 35195 service.settings.initialized = true; 35196 angular.forEach(resp.records, function(record) { 35197 service.data.records[record.uid] = record; 35198 }); 35199 if (resp.order !== undefined) { 35200 service.data.uids_in_order = resp.order;
35201 } 35202 service.data.error = false; 35203 service.status.loading_results = false; 35204 notify_cb(); 35205 }).error( 35206 function(data, status, headers, config) { 35207 // console.error('Can load result'); 35208 service.settings.errorcounter += 1; 35209 service.status.loading_results = false; 35210 service.data.error = status; 35211 }); 35212 }; 35213 service.full = function(uid, setter_cb) { 35214 var record = service.data.records[uid]; 35215 if (record !== undefined && record.full) { 35216 setter_cb(record.full); 35217 return; 35218 } 35219 var serverquery = {'full': uid}; 35220 $http.post(service.settings.url, serverquery).success( 35221 function(resp) { 35222 service.data.records[uid] = resp.full; 35223 setter_cb(resp.full); 35224 }).error( 35225 function(data, status, headers, config) { 35226 // console.error('Can load full info'); 35227 }); 35228 }; 35229 35230 return service; 35231 }]); 35232 35233 /****************************************************************************** 35234 * SERVICE query manager - singleton 35235 */ 35236 35237 var query_service = {}; 35238 query_service.data = {}; 35239 aafapp.factory('query', 35240 [ 35241 '$http', 35242 'webStorage', 35243 'aafcfg', 35244 function($http, webStorage, aafcfg) { 35245 // console.log('Enter Query Service'); 35246 35247 if (query_service.state !== undefined) { 35248 // console.log('-> Re-Use Query Service'); 35249 return query_service; 35250 } 35251 query_service.changed = function() { 35252 /* can be removed (incl. calls */ 35253 }; 35254 query_service.reset = function() { 35255 // console.log('Reset Query'); 35256 query_service.text = ''; 35257 query_service.data.text = ''; 35258 query_service.data.categories = []; 35259 query_service.data.mode = aafcfg.mode; 35260 query_service.data.start = aafcfg.init_start; 35261 query_service.data.end = aafcfg.init_end; 35262 query_service.data.sort = aafcfg.init_sort; 35263 query_service.data.order = aafcfg.init_order; 35264 }; 35265 query_service.initialize = function(uuid, searchterm) { 35266 /* init query */ 35267 query_service.uuid = uuid; 35268 query_service.state = {}; 35269 query_service.reset(); 35270 if (searchterm !== '') { 35271 // console.log('Set searchterm to: ' + searchterm); 35272 query_service.text = searchterm; 35273 query_service.data.text = searchterm; 35274 } 35275 }; 35276 return query_service; 35277 }]); 35278 35279 /****************************************************************************** 35280 * DIRECTIVE result row 35281*/ 35282 35283 aafapp.directive('resultitem', function() { 35284 return { 35285 restrict: 'E', 35286 replace: true, 35287 scope: { 35288 data: '=', 35289 preview: '=', 35290 renderHTML: '&' 35291 }, 35292 templateUrl: '++plone++bda.aaf.site.jsapp/resultitem.xml' 35293 }; 35294 }); 35295 /****************************************************************************** 35296 * DIRECTIVE date time display 35297*/ 35298 aafapp.directive('aafdatetime', function() { 35299 return { 35300 restrict: 'E', 35301 replace: true, 35302 scope: { 35303 dates: '=', 35304 class: '=' 35305 }, 35306 templateUrl: '++plone++bda.aaf.site.jsapp/aafdatetime.xml' 35307 }; 35308 }); 35309 /****************************************************************************** 35310 * CONTROLLER Results area 35311 */ 35312 35313 aafapp.controller('result_ctrl', 35314[ 35315 '$scope', 35316 '$element', 35317 '$window', 35318 '$document', 35319 'result', 35320 'query', 35321 'aafcfg', 35322 '$sce', 35323 function( 35324 $scope, $element, $window, $document, result, query, aafcfg, $sce 35325 ) { 35326 // console.log('Enter Result Controller'); 35327 35328 // fetch settings 35329 result.settings.url = $element.data().resulturl; 35330 35331 // init scope 35332 $scope.results = []; 35333 $scope.topresults = []; 35334 $scope.qdata = query.data; 35335 $scope.state = query.state; 35336 $scope.scroller = false; 35337 $scope.settings = result.settings; 35338 $scope.result_status = result.status; 35339 $scope.preview = {uid: false}; 35340 35341 function process_results() { 35342 // reset data shown 35343 $scope.results = []; 35344 35345 var previous, 35346 missing_uids = [], 35347 size = result.settings.batch_size, 35348 MONTHS = { 35349 1: 'Jänner', 35350 2: 'Februar', 35351 3: 'März', 35352 4: 'April', 35353 5: 'Mai', 35354 6: 'Juni', 35355 7: 'Juli', 35356 8: 'August', 35357 9: 'September', 35358 10: 'Oktober', 35359 11: 'November', 35360 12: 'Dezember', 35361 }; 35362 if (result.data.uids_in_order.length < size) { 35363 size = result.data.uids_in_order.length; 35364 } 35365 for (var i = 0; i < size; i++) { 35366 var uid = result.data.uids_in_order[i], 35367 record = {'meta': {}}; 35368 if (result.data.records[uid]) { 35369 record.info = result.data.records[uid]; 35370 record.meta.index = i; 35371 if (aafcfg.mode == 'calendar' && query.data.sort == 'date' && record.info.dates) { 35372 // we're in dateorder, show months sections 35373 var current = record.info.dates.smonth; 35374 // console.log('previous: ' + previous + '| current: ' + current); 35375 if (previous != current) { 35376 // console.log('changed'); 35377 record.meta.month = MONTHS[current]; 35378 record.meta.year = record.info.dates.syear; 35379 // console.log(record); 35380 previous = current; 35381 } 35382 } 35383 $scope.results.push(record); 35384 } else {
35385 missing_uids.push(uid); 35386 } 35387 } 35388 if (missing_uids.length > 0) { 35389 load_missing(missing_uids); 35390 } 35391 } 35392 function load_query() { 35393 result.load($scope.qdata, false, process_results); 35394 } 35395 function load_missing(missing_uids) { 35396 result.load(false, missing_uids, process_results); 35397 } 35398 35399 $scope.has_more_results = function() { 35400 return (result.data.uids_in_order.length > result.settings.batch_size); 35401 }; 35402 $scope.has_no_results = function() { 35403 return result.data.uids_in_order.length === 0; 35404 }; 35405 $scope.is_initialized = function() { 35406 return result.settings.initialized; 35407 }; 35408 35409 $scope.show_more_results = function() { 35410 if (result.status.loading_results) { 35411 return; 35412 } 35413 if (!$scope.scroller) { 35414 $scope.scroller = true; 35415 } 35416 if (!$scope.has_more_results()) { 35417 return; 35418 } 35419 $('#aafjsapp-search-results').parent().css( 35420 'min-height', 35421 function(){ 35422 return $('#aafjsapp-search-results').height(); 35423 } 35424 ); 35425 result.settings.batch_size += result.settings.batch_delta; 35426 }; 35427 $scope.can_show_more_results = function() { 35428 return $scope.scroller && $scope.has_more_results() && 35429 !result.status.loading_results; 35430 }; 35431 $scope.preview.toggle = function($event, uid, url) { 35432 if ($event.target.className == 'article-link') { 35433 return; 35434 } 35435 $event.preventDefault(); 35436 $scope.preview.data = undefined; 35437 if ($scope.preview.uid && $scope.preview.uid == uid) { 35438 $scope.preview.uid = false; 35439 } else { 35440 $scope.preview.uid = uid; 35441 var record = result.data.records[uid]; 35442 if (!record.full) { 35443 var setter_cb = function(data) { 35444 data.htmltext = $sce.trustAsHtml(data.text); 35445 data.htmldetail_images = []; 35446 angular.forEach(data.detail_images, function(value) { 35447 data.htmldetail_images.push($sce.trustAsHtml(value)); 35448 }); 35449 $scope.preview.data = data; 35450 }; 35451 result.full(uid, setter_cb); 35452 } else { 35453 $scope.preview.data = record; 35454 } 35455 } 35456 }; 35457 // set up watches at the end 35458 $scope.$watch('settings', function(newValue, oldValue) { 35459 process_results(); 35460 }, true); 35461 $scope.$watch('qdata', function(newValue, oldValue) { 35462 load_query(); 35463 }, true); 35464 } 35465]); 35466 35467 /****************************************************************************** 35468 * CONTROLLER Query builder + Sort 35469 */ 35470 35471 aafapp.controller('query_ctrl', 35472 [ 35473 '$scope', 35474 '$element', 35475 '$timeout', 35476 'query', 35477 'aafcfg', 35478 function($scope, $element, $timeout, query, aafcfg) { 35479 // console.log('Enter Query Controller'); 35480 query.initialize($element.data().uuid, $element.data().searchterm); 35481 $scope.data = query.data; 35482 $scope.text = query.data.text; 35483 $scope.state = query.state; 35484 $scope.catsubmenu = false; 35485 $scope.sortsubmenu = false; 35486 $scope.show_categories = aafcfg.mode == 'calendar'; 35487 $scope.toggle_cat_submenu = function() { 35488 $scope.catsubmenu = !$scope.catsubmenu; 35489 $scope.sortsubmenu = false; 35490 }; 35491 $scope.toggle_sort_submenu = function() { 35492 $scope.sortsubmenu = !$scope.sortsubmenu; 35493 $scope.catsubmenu = false; 35494 }; 35495 $scope.is_sorted = function(sortkey) { 35496 return $scope.data.sort == sortkey; 35497 }; 35498 $scope.sort = function(sortkey) { 35499 $scope.data.sort = sortkey; 35500 }; 35501 $scope.is_sortorder = function(sortorder) { 35502 return $scope.data.order == sortorder; 35503 }; 35504 $scope.sortorder = function(sortorder) { 35505 $scope.data.order = sortorder; 35506 }; 35507 $scope.toggle_category = function(category) { 35508 var idx = $scope.data.categories.indexOf(category); 35509 if (idx !== -1) { 35510 $scope.data.categories.splice(idx, 1); 35511 } else { 35512 $scope.data.categories.push(category); 35513 } 35514 }; 35515 $scope.is_selected_category = function(category) { 35516 return ($scope.data.categories.indexOf(category) !== -1); 35517 }; 35518 $scope.reset_query = function() { 35519 query.reset(); 35520 }; 35521 // set up watches at the end 35522 var filter_timeout; 35523 $scope.$watch('data', function(newValue, oldValue) { 35524 $scope.catsubmenu = false;
35525 $scope.sortsubmenu = false; 35526 query.changed(); 35527 }, true); 35528 $scope.$watch('text', function(newValue, oldValue) { 35529 if (newValue.text != oldValue.text) { 35530 $scope.catsubmenu = false; 35531 $scope.sortsubmenu = false; 35532 } 35533 if (filter_timeout) { 35534 $timeout.cancel(filter_timeout); 35535 } 35536 filter_timeout = $timeout( 35537 function() { 35538 $scope.data.text = $scope.text; 35539 }, 35540 500 35541 ); 35542 }, true); 35543 } 35544]); 35545 35546})(angular, jQuery); 35547 35548}catch(e){ 35549 // log it 35550 if (typeof console !== undefined){ 35551 console.log('Error loading javascripts!' + e); 35552 } 35553}finally{ 35554 define = _old_define; 35555 require = _old_require; 35556} 35557
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.