├── .gitattributes ├── .gitignore ├── LICENSE ├── LICENSE-ng ├── README.md ├── nuget ├── build.ps1 ├── publish.ps1 └── src │ ├── AngularJS.Intellisense.nuspec │ └── content │ └── scripts │ └── _references.js └── src ├── AngularJS-VisualStudio-Intellisense.csproj ├── AngularJS-VisualStudio-Intellisense.sln ├── Scripts ├── _references.js ├── angular-animate.js ├── angular-route.js ├── angular.intellisense.js ├── angular.js └── tests │ ├── basic-tests.js │ ├── multi-file-test │ ├── multi-file-test1.js │ └── multi-file-test2.js │ └── test-ngModuleIncludedByDefault.js └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | #packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | 158 | 159 | # Nuget Building stuff 160 | nuget/src/content/scripts/angular.intellisense.js 161 | nuget/nuget.exe 162 | nuget/AngularJS.Intellisense.1.0.0.nupkg 163 | nuget/src/content/scripts/angular.intellisense.js 164 | nuget/nuget.exe 165 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 John Bledsoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LICENSE-ng: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JavaScript IntelliSense for AngularJS Components in Visual Studio 2 | =================================== 3 | 4 | This project provides intellisense in JavaScript files for injected AngularJS components in Visual Studio. 5 | 6 | Visual Studio 2013 provides intellisense for AngularJS directives within HTML pages, but doesn't include support for intellisense on custom AngularJS components in your project. This extension is designed to provide this support, so that you can have full member listing and statement completion on AngularJS services, factories and providers that you develop. 7 | 8 | >**Note:** This extension is now included by default in Visual Studio 2015. If you want to modify the default behavior, this extension is installed by default in the %PROGRAMFILES%\Microsoft Visual Studio 14.0\JavaScript\References folder. %PROGRAMFILES% is the location of your program files folder, usually under C:\Program Files or C:\Program Files (x86). You can overwrite this file with a new copy from this repository, but do so at your own risk. 9 | 10 | ## Usage 11 | 12 | Enabling intellisense for your custom AngularJS components is simple and can be accomplished with a few simple steps: 13 | 14 | ### Prerequisites 15 | 16 | In order for AngularJS Intellisense to function properly, your project needs a correctly-constructed _references.js file. See the following articles for guidance on creating a _references.js file: 17 | 18 | - http://madskristensen.net/post/the-story-behind-_referencesjs 19 | - http://blogs.msdn.com/b/webdev/archive/2014/10/10/references-js-file-s-auto-sync-feature.aspx 20 | 21 | ### Enabling Intellisense in a Single Project 22 | 23 | Add the [angular.intellisense.js](https://raw.github.com/jmbledsoe/angularjs-visualstudio-intellisense/master/src/Scripts/angular.intellisense.js) file to the same folder in your project that contains angular.js or angular.min.js. 24 | 25 | That's it! All of your AngularJS components should now be fully available via intellisense. 26 | 27 | ### Enabling Intellisense in Multiple Projects 28 | 29 | If you prefer to enable AngularJS intellisene for all of your web projects, simply save the [angular.intellisense.js](https://raw.github.com/jmbledsoe/angularjs-visualstudio-intellisense/master/src/Scripts/angular.intellisense.js) file to your %PROGRAMFILES%\Microsoft Visual Studio 12.0\JavaScript\References folder. %PROGRAMFILES% is the location of your program files folder, usually under C:\Program Files or C:\Program Files (x86). 30 | 31 | ## Tips & Tricks 32 | 33 | 1. Intellisense doesn't yet work for private JavaScript functions inside AngularJS components, but this feature will be added in a future version. **Update:** The latest revision of the _intellisense.js file added support for some private functions along some code paths. Basically, the Visual Studio JavaScript intellisense engine works by calling the JavaScript methods in a code file. The latest update calls many more methods, but still misses methods that are only called in "non-default" code paths. I'm hoping that I can gain some insight into this engine and fix this issue. 34 | 35 | ## Project Status 36 | 37 | This project is still early in its life and so may still have issues, but it is stable enough for use as-is. It provides intellisense for AngularJS components in most of the cases I have tested, although there are some code structures that it cannot figure out yet. If you encounter any issues or any scenarios in which the project isn't providing proper intellisense, please submit an issue. 38 | 39 | **Update: 3/27/2015** 40 | 41 | NuGet Package Created: [AngularJS.Intellisense](https://www.nuget.org/packages/AngularJS.Intellisense/) 42 | 43 | ~~~ 44 | PM> Install-Package AngularJS.Intellisense 45 | 46 | ~~~ 47 | 48 | 49 | 50 | **Update: 11/10/2014** 51 | 52 | Refactored the extension so that it works with the Visual Studio JavaScript editor's native extensibility model - renaming the extension to angular.intellisense.js. Now, if the file is placed next to angular.js in the project, it will automatically be loaded right after Angular library. 53 | 54 | Other bug fixes made: 55 | 56 | 1. Modules no longer need to be exported to global 57 | 2. The implicit "ng" module is now required by default for all modules - the same as Angular's runtime behavior 58 | 3. Required modules are respected correctly when editing the first file in which a module is defined 59 | 4. Expanded test cases 60 | 61 | **Update: 10/23/2014** 62 | 63 | I've made some significant changes to the _intellisense.js file, which should enable intellisense in far more parts of code. Specifically, I've added code to enable intellisense for: 64 | 65 | 1. Promise callbacks 66 | 2. Scope event listeners 67 | 3. HTTP service callbacks 68 | 4. Functions exposed as members of services 69 | 70 | There are definitely still issues to work out, and I appreciate any issue reports that I receive. Feel free to submit pull requests as well to solve any issues that you encounter, as I don't have a ton of time to devote to this project. 71 | -------------------------------------------------------------------------------- /nuget/build.ps1: -------------------------------------------------------------------------------- 1 | Copy-Item '..\src\scripts\angular.intellisense.js' -Destination '.\src\content\scripts' -Force 2 | 3 | wget http://nuget.org/nuget.exe -OutFile nuget.exe -ErrorAction Continue 4 | 5 | if (Test-Path '.\nuget.exe'){ 6 | $buildCmd = {.\nuget pack src\AngularJS.Intellisense.nuspec -BasePath src -OutputDirectory .} 7 | Invoke-Command $buildCmd 8 | } 9 | else { 10 | Write-Error "Nuget.exe not found" 11 | } 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /nuget/publish.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Mandatory=$True)] 4 | [string]$apiKey, 5 | [Parameter(Mandatory=$True)] 6 | [string]$packageFile 7 | ) 8 | 9 | #$apiKey = Read-Host -Prompt "What is your API Key?" 10 | 11 | wget http://nuget.org/nuget.exe -OutFile nuget.exe -ErrorAction Continue 12 | 13 | 14 | if (Test-Path '.\nuget.exe'){ 15 | $pubCmd = {.\nuget.exe push $packageFile -ApiKey $apiKey} 16 | Invoke-Command $pubCmd 17 | } 18 | else { 19 | Write-Error "Nuget.exe not found" 20 | } 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /nuget/src/AngularJS.Intellisense.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | AngularJS.Intellisense 5 | 1.0.3 6 | AngularJS Intellilsense add on 7 | Shawn Cicoria, John Bledsoe 8 | Shawn Cicoria, John Bledsoe 9 | https://raw.githubusercontent.com/cicorias/angularjs-visualstudio-intellisense/master/LICENSE-ng 10 | https://angularjs.org/ 11 | https://secure.gravatar.com/avatar/6e1b5ab3ef1593413f1bee4e5a6e6ae7?s=140&d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png 12 | false 13 | This will inject the angularJs intellisense file to your project, along with adding a _references.js file that allows for auto-update of references to drive intellisense. 14 | 15 | For the auto-update of _references.js you can disable by setting the flag to 'false' 16 | Provides project level support for AngularJS intellisence using an intellisense file from John Bledsoe (jmbledsoe) - https://github.com/jmbledsoe/angularjs-visualstudio-intellisense. 17 | 18 | 1.0.3 - set min ref to 1.3 and less than 1.4 19 | 1.0.1 - minor fix - polluted _references.js file 20 | 1.0.0 - initial release 21 | DISCLAIMER: This package is maintained by Shawn Cicoria, but is based upon the hard word of John Beldsoe who created the Intellisense support for AngularJS in Visual Studio. See his GitHub repository 22 | 2015 23 | angularjs, javascript, intellisense 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /nuget/src/content/scripts/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/AngularJS-VisualStudio-Intellisense.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {9FD86549-6793-45BB-A421-B52A527EDBED} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | AngularJS_VisualStudio_Intellisense 15 | AngularJS-VisualStudio-Intellisense 16 | v4.5 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 10.0 76 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | True 86 | True 87 | 1591 88 | / 89 | http://localhost:1591/ 90 | False 91 | False 92 | 93 | 94 | False 95 | 96 | 97 | 98 | 99 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/AngularJS-VisualStudio-Intellisense.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30110.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AngularJS-VisualStudio-Intellisense", "AngularJS-VisualStudio-Intellisense.csproj", "{9FD86549-6793-45BB-A421-B52A527EDBED}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9FD86549-6793-45BB-A421-B52A527EDBED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9FD86549-6793-45BB-A421-B52A527EDBED}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9FD86549-6793-45BB-A421-B52A527EDBED}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9FD86549-6793-45BB-A421-B52A527EDBED}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/Scripts/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /src/Scripts/angular-animate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.26 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /* jshint maxlen: false */ 9 | 10 | /** 11 | * @ngdoc module 12 | * @name ngAnimate 13 | * @description 14 | * 15 | * # ngAnimate 16 | * 17 | * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives. 18 | * 19 | * 20 | *
21 | * 22 | * # Usage 23 | * 24 | * To see animations in action, all that is required is to define the appropriate CSS classes 25 | * or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are: 26 | * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation 27 | * by using the `$animate` service. 28 | * 29 | * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives: 30 | * 31 | * | Directive | Supported Animations | 32 | * |---------------------------------------------------------- |----------------------------------------------------| 33 | * | {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move | 34 | * | {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave | 35 | * | {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave | 36 | * | {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave | 37 | * | {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave | 38 | * | {@link ng.directive:ngClass#usage_animations ngClass} | add and remove | 39 | * | {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) | 40 | * | {@link ng.directive:form#usage_animations form} | add and remove (dirty, pristine, valid, invalid & all other validations) | 41 | * | {@link ng.directive:ngModel#usage_animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) | 42 | * 43 | * You can find out more information about animations upon visiting each directive page. 44 | * 45 | * Below is an example of how to apply animations to a directive that supports animation hooks: 46 | * 47 | * ```html 48 | * 59 | * 60 | * 64 | * 65 | * ``` 66 | * 67 | * Keep in mind that, by default, if an animation is running, any child elements cannot be animated 68 | * until the parent element's animation has completed. This blocking feature can be overridden by 69 | * placing the `ng-animate-children` attribute on a parent container tag. 70 | * 71 | * ```html 72 | *
73 | *
74 | *
75 | * ... 76 | *
77 | *
78 | *
79 | * ``` 80 | * 81 | * When the `on` expression value changes and an animation is triggered then each of the elements within 82 | * will all animate without the block being applied to child elements. 83 | * 84 | *

CSS-defined Animations

85 | * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes 86 | * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported 87 | * and can be used to play along with this naming structure. 88 | * 89 | * The following code below demonstrates how to perform animations using **CSS transitions** with Angular: 90 | * 91 | * ```html 92 | * 115 | * 116 | *
117 | *
118 | *
119 | * ``` 120 | * 121 | * The following code below demonstrates how to perform animations using **CSS animations** with Angular: 122 | * 123 | * ```html 124 | * 138 | * 139 | *
140 | *
141 | *
142 | * ``` 143 | * 144 | * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing. 145 | * 146 | * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add 147 | * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically 148 | * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be 149 | * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end 150 | * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element 151 | * has no CSS transition/animation classes applied to it. 152 | * 153 | *

CSS Staggering Animations

154 | * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a 155 | * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be 156 | * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for 157 | * the animation. The style property expected within the stagger class can either be a **transition-delay** or an 158 | * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). 159 | * 160 | * ```css 161 | * .my-animation.ng-enter { 162 | * /* standard transition code */ 163 | * -webkit-transition: 1s linear all; 164 | * transition: 1s linear all; 165 | * opacity:0; 166 | * } 167 | * .my-animation.ng-enter-stagger { 168 | * /* this will have a 100ms delay between each successive leave animation */ 169 | * -webkit-transition-delay: 0.1s; 170 | * transition-delay: 0.1s; 171 | * 172 | * /* in case the stagger doesn't work then these two values 173 | * must be set to 0 to avoid an accidental CSS inheritance */ 174 | * -webkit-transition-duration: 0s; 175 | * transition-duration: 0s; 176 | * } 177 | * .my-animation.ng-enter.ng-enter-active { 178 | * /* standard transition styles */ 179 | * opacity:1; 180 | * } 181 | * ``` 182 | * 183 | * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations 184 | * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this 185 | * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation 186 | * will also be reset if more than 10ms has passed after the last animation has been fired. 187 | * 188 | * The following code will issue the **ng-leave-stagger** event on the element provided: 189 | * 190 | * ```js 191 | * var kids = parent.children(); 192 | * 193 | * $animate.leave(kids[0]); //stagger index=0 194 | * $animate.leave(kids[1]); //stagger index=1 195 | * $animate.leave(kids[2]); //stagger index=2 196 | * $animate.leave(kids[3]); //stagger index=3 197 | * $animate.leave(kids[4]); //stagger index=4 198 | * 199 | * $timeout(function() { 200 | * //stagger has reset itself 201 | * $animate.leave(kids[5]); //stagger index=0 202 | * $animate.leave(kids[6]); //stagger index=1 203 | * }, 100, false); 204 | * ``` 205 | * 206 | * Stagger animations are currently only supported within CSS-defined animations. 207 | * 208 | *

JavaScript-defined Animations

209 | * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not 210 | * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module. 211 | * 212 | * ```js 213 | * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application. 214 | * var ngModule = angular.module('YourApp', ['ngAnimate']); 215 | * ngModule.animation('.my-crazy-animation', function() { 216 | * return { 217 | * enter: function(element, done) { 218 | * //run the animation here and call done when the animation is complete 219 | * return function(cancelled) { 220 | * //this (optional) function will be called when the animation 221 | * //completes or when the animation is cancelled (the cancelled 222 | * //flag will be set to true if cancelled). 223 | * }; 224 | * }, 225 | * leave: function(element, done) { }, 226 | * move: function(element, done) { }, 227 | * 228 | * //animation that can be triggered before the class is added 229 | * beforeAddClass: function(element, className, done) { }, 230 | * 231 | * //animation that can be triggered after the class is added 232 | * addClass: function(element, className, done) { }, 233 | * 234 | * //animation that can be triggered before the class is removed 235 | * beforeRemoveClass: function(element, className, done) { }, 236 | * 237 | * //animation that can be triggered after the class is removed 238 | * removeClass: function(element, className, done) { } 239 | * }; 240 | * }); 241 | * ``` 242 | * 243 | * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run 244 | * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits 245 | * the element's CSS class attribute value and then run the matching animation event function (if found). 246 | * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will 247 | * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported). 248 | * 249 | * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned. 250 | * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run, 251 | * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation 252 | * or transition code that is defined via a stylesheet). 253 | * 254 | */ 255 | 256 | angular.module('ngAnimate', ['ng']) 257 | 258 | /** 259 | * @ngdoc provider 260 | * @name $animateProvider 261 | * @description 262 | * 263 | * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module. 264 | * When an animation is triggered, the $animate service will query the $animate service to find any animations that match 265 | * the provided name value. 266 | * 267 | * Requires the {@link ngAnimate `ngAnimate`} module to be installed. 268 | * 269 | * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. 270 | * 271 | */ 272 | .directive('ngAnimateChildren', function() { 273 | var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; 274 | return function(scope, element, attrs) { 275 | var val = attrs.ngAnimateChildren; 276 | if(angular.isString(val) && val.length === 0) { //empty attribute 277 | element.data(NG_ANIMATE_CHILDREN, true); 278 | } else { 279 | scope.$watch(val, function(value) { 280 | element.data(NG_ANIMATE_CHILDREN, !!value); 281 | }); 282 | } 283 | }; 284 | }) 285 | 286 | //this private service is only used within CSS-enabled animations 287 | //IE8 + IE9 do not support rAF natively, but that is fine since they 288 | //also don't support transitions and keyframes which means that the code 289 | //below will never be used by the two browsers. 290 | .factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) { 291 | var bod = $document[0].body; 292 | return function(fn) { 293 | //the returned function acts as the cancellation function 294 | return $$rAF(function() { 295 | //the line below will force the browser to perform a repaint 296 | //so that all the animated elements within the animation frame 297 | //will be properly updated and drawn on screen. This is 298 | //required to perform multi-class CSS based animations with 299 | //Firefox. DO NOT REMOVE THIS LINE. 300 | var a = bod.offsetWidth + 1; 301 | fn(); 302 | }); 303 | }; 304 | }]) 305 | 306 | .config(['$provide', '$animateProvider', function($provide, $animateProvider) { 307 | var noop = angular.noop; 308 | var forEach = angular.forEach; 309 | var selectors = $animateProvider.$$selectors; 310 | 311 | var ELEMENT_NODE = 1; 312 | var NG_ANIMATE_STATE = '$$ngAnimateState'; 313 | var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren'; 314 | var NG_ANIMATE_CLASS_NAME = 'ng-animate'; 315 | var rootAnimateState = {running: true}; 316 | 317 | function extractElementNode(element) { 318 | for(var i = 0; i < element.length; i++) { 319 | var elm = element[i]; 320 | if(elm.nodeType == ELEMENT_NODE) { 321 | return elm; 322 | } 323 | } 324 | } 325 | 326 | function prepareElement(element) { 327 | return element && angular.element(element); 328 | } 329 | 330 | function stripCommentsFromElement(element) { 331 | return angular.element(extractElementNode(element)); 332 | } 333 | 334 | function isMatchingElement(elm1, elm2) { 335 | return extractElementNode(elm1) == extractElementNode(elm2); 336 | } 337 | 338 | $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', 339 | function($delegate, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) { 340 | 341 | var globalAnimationCounter = 0; 342 | $rootElement.data(NG_ANIMATE_STATE, rootAnimateState); 343 | 344 | // disable animations during bootstrap, but once we bootstrapped, wait again 345 | // for another digest until enabling animations. The reason why we digest twice 346 | // is because all structural animations (enter, leave and move) all perform a 347 | // post digest operation before animating. If we only wait for a single digest 348 | // to pass then the structural animation would render its animation on page load. 349 | // (which is what we're trying to avoid when the application first boots up.) 350 | $rootScope.$$postDigest(function() { 351 | $rootScope.$$postDigest(function() { 352 | rootAnimateState.running = false; 353 | }); 354 | }); 355 | 356 | var classNameFilter = $animateProvider.classNameFilter(); 357 | var isAnimatableClassName = !classNameFilter 358 | ? function() { return true; } 359 | : function(className) { 360 | return classNameFilter.test(className); 361 | }; 362 | 363 | function blockElementAnimations(element) { 364 | var data = element.data(NG_ANIMATE_STATE) || {}; 365 | data.running = true; 366 | element.data(NG_ANIMATE_STATE, data); 367 | } 368 | 369 | function lookup(name) { 370 | if (name) { 371 | var matches = [], 372 | flagMap = {}, 373 | classes = name.substr(1).split('.'); 374 | 375 | //the empty string value is the default animation 376 | //operation which performs CSS transition and keyframe 377 | //animations sniffing. This is always included for each 378 | //element animation procedure if the browser supports 379 | //transitions and/or keyframe animations. The default 380 | //animation is added to the top of the list to prevent 381 | //any previous animations from affecting the element styling 382 | //prior to the element being animated. 383 | if ($sniffer.transitions || $sniffer.animations) { 384 | matches.push($injector.get(selectors[''])); 385 | } 386 | 387 | for(var i=0; i < classes.length; i++) { 388 | var klass = classes[i], 389 | selectorFactoryName = selectors[klass]; 390 | if(selectorFactoryName && !flagMap[klass]) { 391 | matches.push($injector.get(selectorFactoryName)); 392 | flagMap[klass] = true; 393 | } 394 | } 395 | return matches; 396 | } 397 | } 398 | 399 | function animationRunner(element, animationEvent, className) { 400 | //transcluded directives may sometimes fire an animation using only comment nodes 401 | //best to catch this early on to prevent any animation operations from occurring 402 | var node = element[0]; 403 | if(!node) { 404 | return; 405 | } 406 | 407 | var isSetClassOperation = animationEvent == 'setClass'; 408 | var isClassBased = isSetClassOperation || 409 | animationEvent == 'addClass' || 410 | animationEvent == 'removeClass'; 411 | 412 | var classNameAdd, classNameRemove; 413 | if(angular.isArray(className)) { 414 | classNameAdd = className[0]; 415 | classNameRemove = className[1]; 416 | className = classNameAdd + ' ' + classNameRemove; 417 | } 418 | 419 | var currentClassName = element.attr('class'); 420 | var classes = currentClassName + ' ' + className; 421 | if(!isAnimatableClassName(classes)) { 422 | return; 423 | } 424 | 425 | var beforeComplete = noop, 426 | beforeCancel = [], 427 | before = [], 428 | afterComplete = noop, 429 | afterCancel = [], 430 | after = []; 431 | 432 | var animationLookup = (' ' + classes).replace(/\s+/g,'.'); 433 | forEach(lookup(animationLookup), function(animationFactory) { 434 | var created = registerAnimation(animationFactory, animationEvent); 435 | if(!created && isSetClassOperation) { 436 | registerAnimation(animationFactory, 'addClass'); 437 | registerAnimation(animationFactory, 'removeClass'); 438 | } 439 | }); 440 | 441 | function registerAnimation(animationFactory, event) { 442 | var afterFn = animationFactory[event]; 443 | var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)]; 444 | if(afterFn || beforeFn) { 445 | if(event == 'leave') { 446 | beforeFn = afterFn; 447 | //when set as null then animation knows to skip this phase 448 | afterFn = null; 449 | } 450 | after.push({ 451 | event : event, fn : afterFn 452 | }); 453 | before.push({ 454 | event : event, fn : beforeFn 455 | }); 456 | return true; 457 | } 458 | } 459 | 460 | function run(fns, cancellations, allCompleteFn) { 461 | var animations = []; 462 | forEach(fns, function(animation) { 463 | animation.fn && animations.push(animation); 464 | }); 465 | 466 | var count = 0; 467 | function afterAnimationComplete(index) { 468 | if(cancellations) { 469 | (cancellations[index] || noop)(); 470 | if(++count < animations.length) return; 471 | cancellations = null; 472 | } 473 | allCompleteFn(); 474 | } 475 | 476 | //The code below adds directly to the array in order to work with 477 | //both sync and async animations. Sync animations are when the done() 478 | //operation is called right away. DO NOT REFACTOR! 479 | forEach(animations, function(animation, index) { 480 | var progress = function() { 481 | afterAnimationComplete(index); 482 | }; 483 | switch(animation.event) { 484 | case 'setClass': 485 | cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress)); 486 | break; 487 | case 'addClass': 488 | cancellations.push(animation.fn(element, classNameAdd || className, progress)); 489 | break; 490 | case 'removeClass': 491 | cancellations.push(animation.fn(element, classNameRemove || className, progress)); 492 | break; 493 | default: 494 | cancellations.push(animation.fn(element, progress)); 495 | break; 496 | } 497 | }); 498 | 499 | if(cancellations && cancellations.length === 0) { 500 | allCompleteFn(); 501 | } 502 | } 503 | 504 | return { 505 | node : node, 506 | event : animationEvent, 507 | className : className, 508 | isClassBased : isClassBased, 509 | isSetClassOperation : isSetClassOperation, 510 | before : function(allCompleteFn) { 511 | beforeComplete = allCompleteFn; 512 | run(before, beforeCancel, function() { 513 | beforeComplete = noop; 514 | allCompleteFn(); 515 | }); 516 | }, 517 | after : function(allCompleteFn) { 518 | afterComplete = allCompleteFn; 519 | run(after, afterCancel, function() { 520 | afterComplete = noop; 521 | allCompleteFn(); 522 | }); 523 | }, 524 | cancel : function() { 525 | if(beforeCancel) { 526 | forEach(beforeCancel, function(cancelFn) { 527 | (cancelFn || noop)(true); 528 | }); 529 | beforeComplete(true); 530 | } 531 | if(afterCancel) { 532 | forEach(afterCancel, function(cancelFn) { 533 | (cancelFn || noop)(true); 534 | }); 535 | afterComplete(true); 536 | } 537 | } 538 | }; 539 | } 540 | 541 | /** 542 | * @ngdoc service 543 | * @name $animate 544 | * @kind function 545 | * 546 | * @description 547 | * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations. 548 | * When any of these operations are run, the $animate service 549 | * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object) 550 | * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run. 551 | * 552 | * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives 553 | * will work out of the box without any extra configuration. 554 | * 555 | * Requires the {@link ngAnimate `ngAnimate`} module to be installed. 556 | * 557 | * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application. 558 | * 559 | */ 560 | return { 561 | /** 562 | * @ngdoc method 563 | * @name $animate#enter 564 | * @kind function 565 | * 566 | * @description 567 | * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once 568 | * the animation is started, the following CSS classes will be present on the element for the duration of the animation: 569 | * 570 | * Below is a breakdown of each step that occurs during enter animation: 571 | * 572 | * | Animation Step | What the element class attribute looks like | 573 | * |----------------------------------------------------------------------------------------------|---------------------------------------------| 574 | * | 1. $animate.enter(...) is called | class="my-animation" | 575 | * | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" | 576 | * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | 577 | * | 4. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" | 578 | * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" | 579 | * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-enter" | 580 | * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" | 581 | * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" | 582 | * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | 583 | * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" | 584 | * 585 | * @param {DOMElement} element the element that will be the focus of the enter animation 586 | * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation 587 | * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation 588 | * @param {function()=} doneCallback the callback function that will be called once the animation is complete 589 | */ 590 | enter : function(element, parentElement, afterElement, doneCallback) { 591 | element = angular.element(element); 592 | parentElement = prepareElement(parentElement); 593 | afterElement = prepareElement(afterElement); 594 | 595 | blockElementAnimations(element); 596 | $delegate.enter(element, parentElement, afterElement); 597 | $rootScope.$$postDigest(function() { 598 | element = stripCommentsFromElement(element); 599 | performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback); 600 | }); 601 | }, 602 | 603 | /** 604 | * @ngdoc method 605 | * @name $animate#leave 606 | * @kind function 607 | * 608 | * @description 609 | * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once 610 | * the animation is started, the following CSS classes will be added for the duration of the animation: 611 | * 612 | * Below is a breakdown of each step that occurs during leave animation: 613 | * 614 | * | Animation Step | What the element class attribute looks like | 615 | * |----------------------------------------------------------------------------------------------|---------------------------------------------| 616 | * | 1. $animate.leave(...) is called | class="my-animation" | 617 | * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | 618 | * | 3. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" | 619 | * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" | 620 | * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-leave" | 621 | * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" | 622 | * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" | 623 | * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | 624 | * | 9. The element is removed from the DOM | ... | 625 | * | 10. The doneCallback() callback is fired (if provided) | ... | 626 | * 627 | * @param {DOMElement} element the element that will be the focus of the leave animation 628 | * @param {function()=} doneCallback the callback function that will be called once the animation is complete 629 | */ 630 | leave : function(element, doneCallback) { 631 | element = angular.element(element); 632 | cancelChildAnimations(element); 633 | blockElementAnimations(element); 634 | $rootScope.$$postDigest(function() { 635 | performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() { 636 | $delegate.leave(element); 637 | }, doneCallback); 638 | }); 639 | }, 640 | 641 | /** 642 | * @ngdoc method 643 | * @name $animate#move 644 | * @kind function 645 | * 646 | * @description 647 | * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or 648 | * add the element directly after the afterElement element if present. Then the move animation will be run. Once 649 | * the animation is started, the following CSS classes will be added for the duration of the animation: 650 | * 651 | * Below is a breakdown of each step that occurs during move animation: 652 | * 653 | * | Animation Step | What the element class attribute looks like | 654 | * |----------------------------------------------------------------------------------------------|---------------------------------------------| 655 | * | 1. $animate.move(...) is called | class="my-animation" | 656 | * | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" | 657 | * | 3. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | 658 | * | 4. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" | 659 | * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" | 660 | * | 6. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate ng-move" | 661 | * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" | 662 | * | 8. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" | 663 | * | 9. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | 664 | * | 10. The doneCallback() callback is fired (if provided) | class="my-animation" | 665 | * 666 | * @param {DOMElement} element the element that will be the focus of the move animation 667 | * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation 668 | * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation 669 | * @param {function()=} doneCallback the callback function that will be called once the animation is complete 670 | */ 671 | move : function(element, parentElement, afterElement, doneCallback) { 672 | element = angular.element(element); 673 | parentElement = prepareElement(parentElement); 674 | afterElement = prepareElement(afterElement); 675 | 676 | cancelChildAnimations(element); 677 | blockElementAnimations(element); 678 | $delegate.move(element, parentElement, afterElement); 679 | $rootScope.$$postDigest(function() { 680 | element = stripCommentsFromElement(element); 681 | performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback); 682 | }); 683 | }, 684 | 685 | /** 686 | * @ngdoc method 687 | * @name $animate#addClass 688 | * 689 | * @description 690 | * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class. 691 | * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide 692 | * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions 693 | * or keyframes are defined on the -add or base CSS class). 694 | * 695 | * Below is a breakdown of each step that occurs during addClass animation: 696 | * 697 | * | Animation Step | What the element class attribute looks like | 698 | * |------------------------------------------------------------------------------------------------|---------------------------------------------| 699 | * | 1. $animate.addClass(element, 'super') is called | class="my-animation" | 700 | * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation ng-animate" | 701 | * | 3. the .super-add class are added to the element | class="my-animation ng-animate super-add" | 702 | * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-add" | 703 | * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation ng-animate super-add" | 704 | * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active" | 705 | * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation super super-add super-add-active" | 706 | * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" | 707 | * | 9. The super class is kept on the element | class="my-animation super" | 708 | * | 10. The doneCallback() callback is fired (if provided) | class="my-animation super" | 709 | * 710 | * @param {DOMElement} element the element that will be animated 711 | * @param {string} className the CSS class that will be added to the element and then animated 712 | * @param {function()=} doneCallback the callback function that will be called once the animation is complete 713 | */ 714 | addClass : function(element, className, doneCallback) { 715 | element = angular.element(element); 716 | element = stripCommentsFromElement(element); 717 | performAnimation('addClass', className, element, null, null, function() { 718 | $delegate.addClass(element, className); 719 | }, doneCallback); 720 | }, 721 | 722 | /** 723 | * @ngdoc method 724 | * @name $animate#removeClass 725 | * 726 | * @description 727 | * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value 728 | * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in 729 | * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if 730 | * no CSS transitions or keyframes are defined on the -remove or base CSS classes). 731 | * 732 | * Below is a breakdown of each step that occurs during removeClass animation: 733 | * 734 | * | Animation Step | What the element class attribute looks like | 735 | * |-----------------------------------------------------------------------------------------------|---------------------------------------------| 736 | * | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" | 737 | * | 2. $animate runs any JavaScript-defined animations on the element | class="my-animation super ng-animate" | 738 | * | 3. the .super-remove class are added to the element | class="my-animation super ng-animate super-remove"| 739 | * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" | 740 | * | 5. $animate waits for 10ms (this performs a reflow) | class="my-animation super ng-animate super-remove" | 741 | * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" | 742 | * | 7. $animate waits for X milliseconds for the animation to complete | class="my-animation ng-animate ng-animate-active super-remove super-remove-active" | 743 | * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" | 744 | * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" | 745 | * 746 | * 747 | * @param {DOMElement} element the element that will be animated 748 | * @param {string} className the CSS class that will be animated and then removed from the element 749 | * @param {function()=} doneCallback the callback function that will be called once the animation is complete 750 | */ 751 | removeClass : function(element, className, doneCallback) { 752 | element = angular.element(element); 753 | element = stripCommentsFromElement(element); 754 | performAnimation('removeClass', className, element, null, null, function() { 755 | $delegate.removeClass(element, className); 756 | }, doneCallback); 757 | }, 758 | 759 | /** 760 | * 761 | * @ngdoc function 762 | * @name $animate#setClass 763 | * @function 764 | * @description Adds and/or removes the given CSS classes to and from the element. 765 | * Once complete, the done() callback will be fired (if provided). 766 | * @param {DOMElement} element the element which will its CSS classes changed 767 | * removed from it 768 | * @param {string} add the CSS classes which will be added to the element 769 | * @param {string} remove the CSS class which will be removed from the element 770 | * @param {Function=} done the callback function (if provided) that will be fired after the 771 | * CSS classes have been set on the element 772 | */ 773 | setClass : function(element, add, remove, doneCallback) { 774 | element = angular.element(element); 775 | element = stripCommentsFromElement(element); 776 | performAnimation('setClass', [add, remove], element, null, null, function() { 777 | $delegate.setClass(element, add, remove); 778 | }, doneCallback); 779 | }, 780 | 781 | /** 782 | * @ngdoc method 783 | * @name $animate#enabled 784 | * @kind function 785 | * 786 | * @param {boolean=} value If provided then set the animation on or off. 787 | * @param {DOMElement=} element If provided then the element will be used to represent the enable/disable operation 788 | * @return {boolean} Current animation state. 789 | * 790 | * @description 791 | * Globally enables/disables animations. 792 | * 793 | */ 794 | enabled : function(value, element) { 795 | switch(arguments.length) { 796 | case 2: 797 | if(value) { 798 | cleanup(element); 799 | } else { 800 | var data = element.data(NG_ANIMATE_STATE) || {}; 801 | data.disabled = true; 802 | element.data(NG_ANIMATE_STATE, data); 803 | } 804 | break; 805 | 806 | case 1: 807 | rootAnimateState.disabled = !value; 808 | break; 809 | 810 | default: 811 | value = !rootAnimateState.disabled; 812 | break; 813 | } 814 | return !!value; 815 | } 816 | }; 817 | 818 | /* 819 | all animations call this shared animation triggering function internally. 820 | The animationEvent variable refers to the JavaScript animation event that will be triggered 821 | and the className value is the name of the animation that will be applied within the 822 | CSS code. Element, parentElement and afterElement are provided DOM elements for the animation 823 | and the onComplete callback will be fired once the animation is fully complete. 824 | */ 825 | function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) { 826 | 827 | var runner = animationRunner(element, animationEvent, className); 828 | if(!runner) { 829 | fireDOMOperation(); 830 | fireBeforeCallbackAsync(); 831 | fireAfterCallbackAsync(); 832 | closeAnimation(); 833 | return; 834 | } 835 | 836 | className = runner.className; 837 | var elementEvents = angular.element._data(runner.node); 838 | elementEvents = elementEvents && elementEvents.events; 839 | 840 | if (!parentElement) { 841 | parentElement = afterElement ? afterElement.parent() : element.parent(); 842 | } 843 | 844 | var ngAnimateState = element.data(NG_ANIMATE_STATE) || {}; 845 | var runningAnimations = ngAnimateState.active || {}; 846 | var totalActiveAnimations = ngAnimateState.totalActive || 0; 847 | var lastAnimation = ngAnimateState.last; 848 | 849 | //only allow animations if the currently running animation is not structural 850 | //or if there is no animation running at all 851 | var skipAnimations; 852 | if (runner.isClassBased) { 853 | skipAnimations = ngAnimateState.running || 854 | ngAnimateState.disabled || 855 | (lastAnimation && !lastAnimation.isClassBased); 856 | } 857 | 858 | //skip the animation if animations are disabled, a parent is already being animated, 859 | //the element is not currently attached to the document body or then completely close 860 | //the animation if any matching animations are not found at all. 861 | //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found. 862 | if (skipAnimations || animationsDisabled(element, parentElement)) { 863 | fireDOMOperation(); 864 | fireBeforeCallbackAsync(); 865 | fireAfterCallbackAsync(); 866 | closeAnimation(); 867 | return; 868 | } 869 | 870 | var skipAnimation = false; 871 | if(totalActiveAnimations > 0) { 872 | var animationsToCancel = []; 873 | if(!runner.isClassBased) { 874 | if(animationEvent == 'leave' && runningAnimations['ng-leave']) { 875 | skipAnimation = true; 876 | } else { 877 | //cancel all animations when a structural animation takes place 878 | for(var klass in runningAnimations) { 879 | animationsToCancel.push(runningAnimations[klass]); 880 | cleanup(element, klass); 881 | } 882 | runningAnimations = {}; 883 | totalActiveAnimations = 0; 884 | } 885 | } else if(lastAnimation.event == 'setClass') { 886 | animationsToCancel.push(lastAnimation); 887 | cleanup(element, className); 888 | } 889 | else if(runningAnimations[className]) { 890 | var current = runningAnimations[className]; 891 | if(current.event == animationEvent) { 892 | skipAnimation = true; 893 | } else { 894 | animationsToCancel.push(current); 895 | cleanup(element, className); 896 | } 897 | } 898 | 899 | if(animationsToCancel.length > 0) { 900 | forEach(animationsToCancel, function(operation) { 901 | operation.cancel(); 902 | }); 903 | } 904 | } 905 | 906 | if(runner.isClassBased && !runner.isSetClassOperation && !skipAnimation) { 907 | skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR 908 | } 909 | 910 | if(skipAnimation) { 911 | fireDOMOperation(); 912 | fireBeforeCallbackAsync(); 913 | fireAfterCallbackAsync(); 914 | fireDoneCallbackAsync(); 915 | return; 916 | } 917 | 918 | if(animationEvent == 'leave') { 919 | //there's no need to ever remove the listener since the element 920 | //will be removed (destroyed) after the leave animation ends or 921 | //is cancelled midway 922 | element.one('$destroy', function(e) { 923 | var element = angular.element(this); 924 | var state = element.data(NG_ANIMATE_STATE); 925 | if(state) { 926 | var activeLeaveAnimation = state.active['ng-leave']; 927 | if(activeLeaveAnimation) { 928 | activeLeaveAnimation.cancel(); 929 | cleanup(element, 'ng-leave'); 930 | } 931 | } 932 | }); 933 | } 934 | 935 | //the ng-animate class does nothing, but it's here to allow for 936 | //parent animations to find and cancel child animations when needed 937 | element.addClass(NG_ANIMATE_CLASS_NAME); 938 | 939 | var localAnimationCount = globalAnimationCounter++; 940 | totalActiveAnimations++; 941 | runningAnimations[className] = runner; 942 | 943 | element.data(NG_ANIMATE_STATE, { 944 | last : runner, 945 | active : runningAnimations, 946 | index : localAnimationCount, 947 | totalActive : totalActiveAnimations 948 | }); 949 | 950 | //first we run the before animations and when all of those are complete 951 | //then we perform the DOM operation and run the next set of animations 952 | fireBeforeCallbackAsync(); 953 | runner.before(function(cancelled) { 954 | var data = element.data(NG_ANIMATE_STATE); 955 | cancelled = cancelled || 956 | !data || !data.active[className] || 957 | (runner.isClassBased && data.active[className].event != animationEvent); 958 | 959 | fireDOMOperation(); 960 | if(cancelled === true) { 961 | closeAnimation(); 962 | } else { 963 | fireAfterCallbackAsync(); 964 | runner.after(closeAnimation); 965 | } 966 | }); 967 | 968 | function fireDOMCallback(animationPhase) { 969 | var eventName = '$animate:' + animationPhase; 970 | if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) { 971 | $$asyncCallback(function() { 972 | element.triggerHandler(eventName, { 973 | event : animationEvent, 974 | className : className 975 | }); 976 | }); 977 | } 978 | } 979 | 980 | function fireBeforeCallbackAsync() { 981 | fireDOMCallback('before'); 982 | } 983 | 984 | function fireAfterCallbackAsync() { 985 | fireDOMCallback('after'); 986 | } 987 | 988 | function fireDoneCallbackAsync() { 989 | fireDOMCallback('close'); 990 | if(doneCallback) { 991 | $$asyncCallback(function() { 992 | doneCallback(); 993 | }); 994 | } 995 | } 996 | 997 | //it is less complicated to use a flag than managing and canceling 998 | //timeouts containing multiple callbacks. 999 | function fireDOMOperation() { 1000 | if(!fireDOMOperation.hasBeenRun) { 1001 | fireDOMOperation.hasBeenRun = true; 1002 | domOperation(); 1003 | } 1004 | } 1005 | 1006 | function closeAnimation() { 1007 | if(!closeAnimation.hasBeenRun) { 1008 | closeAnimation.hasBeenRun = true; 1009 | var data = element.data(NG_ANIMATE_STATE); 1010 | if(data) { 1011 | /* only structural animations wait for reflow before removing an 1012 | animation, but class-based animations don't. An example of this 1013 | failing would be when a parent HTML tag has a ng-class attribute 1014 | causing ALL directives below to skip animations during the digest */ 1015 | if(runner && runner.isClassBased) { 1016 | cleanup(element, className); 1017 | } else { 1018 | $$asyncCallback(function() { 1019 | var data = element.data(NG_ANIMATE_STATE) || {}; 1020 | if(localAnimationCount == data.index) { 1021 | cleanup(element, className, animationEvent); 1022 | } 1023 | }); 1024 | element.data(NG_ANIMATE_STATE, data); 1025 | } 1026 | } 1027 | fireDoneCallbackAsync(); 1028 | } 1029 | } 1030 | } 1031 | 1032 | function cancelChildAnimations(element) { 1033 | var node = extractElementNode(element); 1034 | if (node) { 1035 | var nodes = angular.isFunction(node.getElementsByClassName) ? 1036 | node.getElementsByClassName(NG_ANIMATE_CLASS_NAME) : 1037 | node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME); 1038 | forEach(nodes, function(element) { 1039 | element = angular.element(element); 1040 | var data = element.data(NG_ANIMATE_STATE); 1041 | if(data && data.active) { 1042 | forEach(data.active, function(runner) { 1043 | runner.cancel(); 1044 | }); 1045 | } 1046 | }); 1047 | } 1048 | } 1049 | 1050 | function cleanup(element, className) { 1051 | if(isMatchingElement(element, $rootElement)) { 1052 | if(!rootAnimateState.disabled) { 1053 | rootAnimateState.running = false; 1054 | rootAnimateState.structural = false; 1055 | } 1056 | } else if(className) { 1057 | var data = element.data(NG_ANIMATE_STATE) || {}; 1058 | 1059 | var removeAnimations = className === true; 1060 | if(!removeAnimations && data.active && data.active[className]) { 1061 | data.totalActive--; 1062 | delete data.active[className]; 1063 | } 1064 | 1065 | if(removeAnimations || !data.totalActive) { 1066 | element.removeClass(NG_ANIMATE_CLASS_NAME); 1067 | element.removeData(NG_ANIMATE_STATE); 1068 | } 1069 | } 1070 | } 1071 | 1072 | function animationsDisabled(element, parentElement) { 1073 | if (rootAnimateState.disabled) { 1074 | return true; 1075 | } 1076 | 1077 | if (isMatchingElement(element, $rootElement)) { 1078 | return rootAnimateState.running; 1079 | } 1080 | 1081 | var allowChildAnimations, parentRunningAnimation, hasParent; 1082 | do { 1083 | //the element did not reach the root element which means that it 1084 | //is not apart of the DOM. Therefore there is no reason to do 1085 | //any animations on it 1086 | if (parentElement.length === 0) break; 1087 | 1088 | var isRoot = isMatchingElement(parentElement, $rootElement); 1089 | var state = isRoot ? rootAnimateState : (parentElement.data(NG_ANIMATE_STATE) || {}); 1090 | if (state.disabled) { 1091 | return true; 1092 | } 1093 | 1094 | //no matter what, for an animation to work it must reach the root element 1095 | //this implies that the element is attached to the DOM when the animation is run 1096 | if (isRoot) { 1097 | hasParent = true; 1098 | } 1099 | 1100 | //once a flag is found that is strictly false then everything before 1101 | //it will be discarded and all child animations will be restricted 1102 | if (allowChildAnimations !== false) { 1103 | var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN); 1104 | if(angular.isDefined(animateChildrenFlag)) { 1105 | allowChildAnimations = animateChildrenFlag; 1106 | } 1107 | } 1108 | 1109 | parentRunningAnimation = parentRunningAnimation || 1110 | state.running || 1111 | (state.last && !state.last.isClassBased); 1112 | } 1113 | while(parentElement = parentElement.parent()); 1114 | 1115 | return !hasParent || (!allowChildAnimations && parentRunningAnimation); 1116 | } 1117 | }]); 1118 | 1119 | $animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow', 1120 | function($window, $sniffer, $timeout, $$animateReflow) { 1121 | // Detect proper transitionend/animationend event names. 1122 | var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; 1123 | 1124 | // If unprefixed events are not supported but webkit-prefixed are, use the latter. 1125 | // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them. 1126 | // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend` 1127 | // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`. 1128 | // Register both events in case `window.onanimationend` is not supported because of that, 1129 | // do the same for `transitionend` as Safari is likely to exhibit similar behavior. 1130 | // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit 1131 | // therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition 1132 | if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) { 1133 | CSS_PREFIX = '-webkit-'; 1134 | TRANSITION_PROP = 'WebkitTransition'; 1135 | TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; 1136 | } else { 1137 | TRANSITION_PROP = 'transition'; 1138 | TRANSITIONEND_EVENT = 'transitionend'; 1139 | } 1140 | 1141 | if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) { 1142 | CSS_PREFIX = '-webkit-'; 1143 | ANIMATION_PROP = 'WebkitAnimation'; 1144 | ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; 1145 | } else { 1146 | ANIMATION_PROP = 'animation'; 1147 | ANIMATIONEND_EVENT = 'animationend'; 1148 | } 1149 | 1150 | var DURATION_KEY = 'Duration'; 1151 | var PROPERTY_KEY = 'Property'; 1152 | var DELAY_KEY = 'Delay'; 1153 | var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; 1154 | var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey'; 1155 | var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data'; 1156 | var NG_ANIMATE_BLOCK_CLASS_NAME = 'ng-animate-block-transitions'; 1157 | var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; 1158 | var CLOSING_TIME_BUFFER = 1.5; 1159 | var ONE_SECOND = 1000; 1160 | 1161 | var lookupCache = {}; 1162 | var parentCounter = 0; 1163 | var animationReflowQueue = []; 1164 | var cancelAnimationReflow; 1165 | function afterReflow(element, callback) { 1166 | if(cancelAnimationReflow) { 1167 | cancelAnimationReflow(); 1168 | } 1169 | animationReflowQueue.push(callback); 1170 | cancelAnimationReflow = $$animateReflow(function() { 1171 | forEach(animationReflowQueue, function(fn) { 1172 | fn(); 1173 | }); 1174 | 1175 | animationReflowQueue = []; 1176 | cancelAnimationReflow = null; 1177 | lookupCache = {}; 1178 | }); 1179 | } 1180 | 1181 | var closingTimer = null; 1182 | var closingTimestamp = 0; 1183 | var animationElementQueue = []; 1184 | function animationCloseHandler(element, totalTime) { 1185 | var node = extractElementNode(element); 1186 | element = angular.element(node); 1187 | 1188 | //this item will be garbage collected by the closing 1189 | //animation timeout 1190 | animationElementQueue.push(element); 1191 | 1192 | //but it may not need to cancel out the existing timeout 1193 | //if the timestamp is less than the previous one 1194 | var futureTimestamp = Date.now() + totalTime; 1195 | if(futureTimestamp <= closingTimestamp) { 1196 | return; 1197 | } 1198 | 1199 | $timeout.cancel(closingTimer); 1200 | 1201 | closingTimestamp = futureTimestamp; 1202 | closingTimer = $timeout(function() { 1203 | closeAllAnimations(animationElementQueue); 1204 | animationElementQueue = []; 1205 | }, totalTime, false); 1206 | } 1207 | 1208 | function closeAllAnimations(elements) { 1209 | forEach(elements, function(element) { 1210 | var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY); 1211 | if(elementData) { 1212 | (elementData.closeAnimationFn || noop)(); 1213 | } 1214 | }); 1215 | } 1216 | 1217 | function getElementAnimationDetails(element, cacheKey) { 1218 | var data = cacheKey ? lookupCache[cacheKey] : null; 1219 | if(!data) { 1220 | var transitionDuration = 0; 1221 | var transitionDelay = 0; 1222 | var animationDuration = 0; 1223 | var animationDelay = 0; 1224 | var transitionDelayStyle; 1225 | var animationDelayStyle; 1226 | var transitionDurationStyle; 1227 | var transitionPropertyStyle; 1228 | 1229 | //we want all the styles defined before and after 1230 | forEach(element, function(element) { 1231 | if (element.nodeType == ELEMENT_NODE) { 1232 | var elementStyles = $window.getComputedStyle(element) || {}; 1233 | 1234 | transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY]; 1235 | 1236 | transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration); 1237 | 1238 | transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY]; 1239 | 1240 | transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY]; 1241 | 1242 | transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay); 1243 | 1244 | animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY]; 1245 | 1246 | animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay); 1247 | 1248 | var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]); 1249 | 1250 | if(aDuration > 0) { 1251 | aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1; 1252 | } 1253 | 1254 | animationDuration = Math.max(aDuration, animationDuration); 1255 | } 1256 | }); 1257 | data = { 1258 | total : 0, 1259 | transitionPropertyStyle: transitionPropertyStyle, 1260 | transitionDurationStyle: transitionDurationStyle, 1261 | transitionDelayStyle: transitionDelayStyle, 1262 | transitionDelay: transitionDelay, 1263 | transitionDuration: transitionDuration, 1264 | animationDelayStyle: animationDelayStyle, 1265 | animationDelay: animationDelay, 1266 | animationDuration: animationDuration 1267 | }; 1268 | if(cacheKey) { 1269 | lookupCache[cacheKey] = data; 1270 | } 1271 | } 1272 | return data; 1273 | } 1274 | 1275 | function parseMaxTime(str) { 1276 | var maxValue = 0; 1277 | var values = angular.isString(str) ? 1278 | str.split(/\s*,\s*/) : 1279 | []; 1280 | forEach(values, function(value) { 1281 | maxValue = Math.max(parseFloat(value) || 0, maxValue); 1282 | }); 1283 | return maxValue; 1284 | } 1285 | 1286 | function getCacheKey(element) { 1287 | var parentElement = element.parent(); 1288 | var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY); 1289 | if(!parentID) { 1290 | parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter); 1291 | parentID = parentCounter; 1292 | } 1293 | return parentID + '-' + extractElementNode(element).getAttribute('class'); 1294 | } 1295 | 1296 | function animateSetup(animationEvent, element, className, calculationDecorator) { 1297 | var cacheKey = getCacheKey(element); 1298 | var eventCacheKey = cacheKey + ' ' + className; 1299 | var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0; 1300 | 1301 | var stagger = {}; 1302 | if(itemIndex > 0) { 1303 | var staggerClassName = className + '-stagger'; 1304 | var staggerCacheKey = cacheKey + ' ' + staggerClassName; 1305 | var applyClasses = !lookupCache[staggerCacheKey]; 1306 | 1307 | applyClasses && element.addClass(staggerClassName); 1308 | 1309 | stagger = getElementAnimationDetails(element, staggerCacheKey); 1310 | 1311 | applyClasses && element.removeClass(staggerClassName); 1312 | } 1313 | 1314 | /* the animation itself may need to add/remove special CSS classes 1315 | * before calculating the anmation styles */ 1316 | calculationDecorator = calculationDecorator || 1317 | function(fn) { return fn(); }; 1318 | 1319 | element.addClass(className); 1320 | 1321 | var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {}; 1322 | 1323 | var timings = calculationDecorator(function() { 1324 | return getElementAnimationDetails(element, eventCacheKey); 1325 | }); 1326 | 1327 | var transitionDuration = timings.transitionDuration; 1328 | var animationDuration = timings.animationDuration; 1329 | if(transitionDuration === 0 && animationDuration === 0) { 1330 | element.removeClass(className); 1331 | return false; 1332 | } 1333 | 1334 | element.data(NG_ANIMATE_CSS_DATA_KEY, { 1335 | running : formerData.running || 0, 1336 | itemIndex : itemIndex, 1337 | stagger : stagger, 1338 | timings : timings, 1339 | closeAnimationFn : noop 1340 | }); 1341 | 1342 | //temporarily disable the transition so that the enter styles 1343 | //don't animate twice (this is here to avoid a bug in Chrome/FF). 1344 | var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass'; 1345 | if(transitionDuration > 0) { 1346 | blockTransitions(element, className, isCurrentlyAnimating); 1347 | } 1348 | 1349 | //staggering keyframe animations work by adjusting the `animation-delay` CSS property 1350 | //on the given element, however, the delay value can only calculated after the reflow 1351 | //since by that time $animate knows how many elements are being animated. Therefore, 1352 | //until the reflow occurs the element needs to be blocked (where the keyframe animation 1353 | //is set to `none 0s`). This blocking mechanism should only be set for when a stagger 1354 | //animation is detected and when the element item index is greater than 0. 1355 | if(animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) { 1356 | blockKeyframeAnimations(element); 1357 | } 1358 | 1359 | return true; 1360 | } 1361 | 1362 | function isStructuralAnimation(className) { 1363 | return className == 'ng-enter' || className == 'ng-move' || className == 'ng-leave'; 1364 | } 1365 | 1366 | function blockTransitions(element, className, isAnimating) { 1367 | if(isStructuralAnimation(className) || !isAnimating) { 1368 | extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none'; 1369 | } else { 1370 | element.addClass(NG_ANIMATE_BLOCK_CLASS_NAME); 1371 | } 1372 | } 1373 | 1374 | function blockKeyframeAnimations(element) { 1375 | extractElementNode(element).style[ANIMATION_PROP] = 'none 0s'; 1376 | } 1377 | 1378 | function unblockTransitions(element, className) { 1379 | var prop = TRANSITION_PROP + PROPERTY_KEY; 1380 | var node = extractElementNode(element); 1381 | if(node.style[prop] && node.style[prop].length > 0) { 1382 | node.style[prop] = ''; 1383 | } 1384 | element.removeClass(NG_ANIMATE_BLOCK_CLASS_NAME); 1385 | } 1386 | 1387 | function unblockKeyframeAnimations(element) { 1388 | var prop = ANIMATION_PROP; 1389 | var node = extractElementNode(element); 1390 | if(node.style[prop] && node.style[prop].length > 0) { 1391 | node.style[prop] = ''; 1392 | } 1393 | } 1394 | 1395 | function animateRun(animationEvent, element, className, activeAnimationComplete) { 1396 | var node = extractElementNode(element); 1397 | var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY); 1398 | if(node.getAttribute('class').indexOf(className) == -1 || !elementData) { 1399 | activeAnimationComplete(); 1400 | return; 1401 | } 1402 | 1403 | var activeClassName = ''; 1404 | forEach(className.split(' '), function(klass, i) { 1405 | activeClassName += (i > 0 ? ' ' : '') + klass + '-active'; 1406 | }); 1407 | 1408 | var stagger = elementData.stagger; 1409 | var timings = elementData.timings; 1410 | var itemIndex = elementData.itemIndex; 1411 | var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration); 1412 | var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay); 1413 | var maxDelayTime = maxDelay * ONE_SECOND; 1414 | 1415 | var startTime = Date.now(); 1416 | var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT; 1417 | 1418 | var style = '', appliedStyles = []; 1419 | if(timings.transitionDuration > 0) { 1420 | var propertyStyle = timings.transitionPropertyStyle; 1421 | if(propertyStyle.indexOf('all') == -1) { 1422 | style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';'; 1423 | style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';'; 1424 | appliedStyles.push(CSS_PREFIX + 'transition-property'); 1425 | appliedStyles.push(CSS_PREFIX + 'transition-duration'); 1426 | } 1427 | } 1428 | 1429 | if(itemIndex > 0) { 1430 | if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) { 1431 | var delayStyle = timings.transitionDelayStyle; 1432 | style += CSS_PREFIX + 'transition-delay: ' + 1433 | prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; '; 1434 | appliedStyles.push(CSS_PREFIX + 'transition-delay'); 1435 | } 1436 | 1437 | if(stagger.animationDelay > 0 && stagger.animationDuration === 0) { 1438 | style += CSS_PREFIX + 'animation-delay: ' + 1439 | prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; '; 1440 | appliedStyles.push(CSS_PREFIX + 'animation-delay'); 1441 | } 1442 | } 1443 | 1444 | if(appliedStyles.length > 0) { 1445 | //the element being animated may sometimes contain comment nodes in 1446 | //the jqLite object, so we're safe to use a single variable to house 1447 | //the styles since there is always only one element being animated 1448 | var oldStyle = node.getAttribute('style') || ''; 1449 | node.setAttribute('style', oldStyle + '; ' + style); 1450 | } 1451 | 1452 | element.on(css3AnimationEvents, onAnimationProgress); 1453 | element.addClass(activeClassName); 1454 | elementData.closeAnimationFn = function() { 1455 | onEnd(); 1456 | activeAnimationComplete(); 1457 | }; 1458 | 1459 | var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0); 1460 | var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER; 1461 | var totalTime = (staggerTime + animationTime) * ONE_SECOND; 1462 | 1463 | elementData.running++; 1464 | animationCloseHandler(element, totalTime); 1465 | return onEnd; 1466 | 1467 | // This will automatically be called by $animate so 1468 | // there is no need to attach this internally to the 1469 | // timeout done method. 1470 | function onEnd(cancelled) { 1471 | element.off(css3AnimationEvents, onAnimationProgress); 1472 | element.removeClass(activeClassName); 1473 | animateClose(element, className); 1474 | var node = extractElementNode(element); 1475 | for (var i in appliedStyles) { 1476 | node.style.removeProperty(appliedStyles[i]); 1477 | } 1478 | } 1479 | 1480 | function onAnimationProgress(event) { 1481 | event.stopPropagation(); 1482 | var ev = event.originalEvent || event; 1483 | var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now(); 1484 | 1485 | /* Firefox (or possibly just Gecko) likes to not round values up 1486 | * when a ms measurement is used for the animation */ 1487 | var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); 1488 | 1489 | /* $manualTimeStamp is a mocked timeStamp value which is set 1490 | * within browserTrigger(). This is only here so that tests can 1491 | * mock animations properly. Real events fallback to event.timeStamp, 1492 | * or, if they don't, then a timeStamp is automatically created for them. 1493 | * We're checking to see if the timeStamp surpasses the expected delay, 1494 | * but we're using elapsedTime instead of the timeStamp on the 2nd 1495 | * pre-condition since animations sometimes close off early */ 1496 | if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { 1497 | activeAnimationComplete(); 1498 | } 1499 | } 1500 | } 1501 | 1502 | function prepareStaggerDelay(delayStyle, staggerDelay, index) { 1503 | var style = ''; 1504 | forEach(delayStyle.split(','), function(val, i) { 1505 | style += (i > 0 ? ',' : '') + 1506 | (index * staggerDelay + parseInt(val, 10)) + 's'; 1507 | }); 1508 | return style; 1509 | } 1510 | 1511 | function animateBefore(animationEvent, element, className, calculationDecorator) { 1512 | if(animateSetup(animationEvent, element, className, calculationDecorator)) { 1513 | return function(cancelled) { 1514 | cancelled && animateClose(element, className); 1515 | }; 1516 | } 1517 | } 1518 | 1519 | function animateAfter(animationEvent, element, className, afterAnimationComplete) { 1520 | if(element.data(NG_ANIMATE_CSS_DATA_KEY)) { 1521 | return animateRun(animationEvent, element, className, afterAnimationComplete); 1522 | } else { 1523 | animateClose(element, className); 1524 | afterAnimationComplete(); 1525 | } 1526 | } 1527 | 1528 | function animate(animationEvent, element, className, animationComplete) { 1529 | //If the animateSetup function doesn't bother returning a 1530 | //cancellation function then it means that there is no animation 1531 | //to perform at all 1532 | var preReflowCancellation = animateBefore(animationEvent, element, className); 1533 | if(!preReflowCancellation) { 1534 | animationComplete(); 1535 | return; 1536 | } 1537 | 1538 | //There are two cancellation functions: one is before the first 1539 | //reflow animation and the second is during the active state 1540 | //animation. The first function will take care of removing the 1541 | //data from the element which will not make the 2nd animation 1542 | //happen in the first place 1543 | var cancel = preReflowCancellation; 1544 | afterReflow(element, function() { 1545 | unblockTransitions(element, className); 1546 | unblockKeyframeAnimations(element); 1547 | //once the reflow is complete then we point cancel to 1548 | //the new cancellation function which will remove all of the 1549 | //animation properties from the active animation 1550 | cancel = animateAfter(animationEvent, element, className, animationComplete); 1551 | }); 1552 | 1553 | return function(cancelled) { 1554 | (cancel || noop)(cancelled); 1555 | }; 1556 | } 1557 | 1558 | function animateClose(element, className) { 1559 | element.removeClass(className); 1560 | var data = element.data(NG_ANIMATE_CSS_DATA_KEY); 1561 | if(data) { 1562 | if(data.running) { 1563 | data.running--; 1564 | } 1565 | if(!data.running || data.running === 0) { 1566 | element.removeData(NG_ANIMATE_CSS_DATA_KEY); 1567 | } 1568 | } 1569 | } 1570 | 1571 | return { 1572 | enter : function(element, animationCompleted) { 1573 | return animate('enter', element, 'ng-enter', animationCompleted); 1574 | }, 1575 | 1576 | leave : function(element, animationCompleted) { 1577 | return animate('leave', element, 'ng-leave', animationCompleted); 1578 | }, 1579 | 1580 | move : function(element, animationCompleted) { 1581 | return animate('move', element, 'ng-move', animationCompleted); 1582 | }, 1583 | 1584 | beforeSetClass : function(element, add, remove, animationCompleted) { 1585 | var className = suffixClasses(remove, '-remove') + ' ' + 1586 | suffixClasses(add, '-add'); 1587 | var cancellationMethod = animateBefore('setClass', element, className, function(fn) { 1588 | /* when classes are removed from an element then the transition style 1589 | * that is applied is the transition defined on the element without the 1590 | * CSS class being there. This is how CSS3 functions outside of ngAnimate. 1591 | * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */ 1592 | var klass = element.attr('class'); 1593 | element.removeClass(remove); 1594 | element.addClass(add); 1595 | var timings = fn(); 1596 | element.attr('class', klass); 1597 | return timings; 1598 | }); 1599 | 1600 | if(cancellationMethod) { 1601 | afterReflow(element, function() { 1602 | unblockTransitions(element, className); 1603 | unblockKeyframeAnimations(element); 1604 | animationCompleted(); 1605 | }); 1606 | return cancellationMethod; 1607 | } 1608 | animationCompleted(); 1609 | }, 1610 | 1611 | beforeAddClass : function(element, className, animationCompleted) { 1612 | var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), function(fn) { 1613 | 1614 | /* when a CSS class is added to an element then the transition style that 1615 | * is applied is the transition defined on the element when the CSS class 1616 | * is added at the time of the animation. This is how CSS3 functions 1617 | * outside of ngAnimate. */ 1618 | element.addClass(className); 1619 | var timings = fn(); 1620 | element.removeClass(className); 1621 | return timings; 1622 | }); 1623 | 1624 | if(cancellationMethod) { 1625 | afterReflow(element, function() { 1626 | unblockTransitions(element, className); 1627 | unblockKeyframeAnimations(element); 1628 | animationCompleted(); 1629 | }); 1630 | return cancellationMethod; 1631 | } 1632 | animationCompleted(); 1633 | }, 1634 | 1635 | setClass : function(element, add, remove, animationCompleted) { 1636 | remove = suffixClasses(remove, '-remove'); 1637 | add = suffixClasses(add, '-add'); 1638 | var className = remove + ' ' + add; 1639 | return animateAfter('setClass', element, className, animationCompleted); 1640 | }, 1641 | 1642 | addClass : function(element, className, animationCompleted) { 1643 | return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted); 1644 | }, 1645 | 1646 | beforeRemoveClass : function(element, className, animationCompleted) { 1647 | var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), function(fn) { 1648 | /* when classes are removed from an element then the transition style 1649 | * that is applied is the transition defined on the element without the 1650 | * CSS class being there. This is how CSS3 functions outside of ngAnimate. 1651 | * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */ 1652 | var klass = element.attr('class'); 1653 | element.removeClass(className); 1654 | var timings = fn(); 1655 | element.attr('class', klass); 1656 | return timings; 1657 | }); 1658 | 1659 | if(cancellationMethod) { 1660 | afterReflow(element, function() { 1661 | unblockTransitions(element, className); 1662 | unblockKeyframeAnimations(element); 1663 | animationCompleted(); 1664 | }); 1665 | return cancellationMethod; 1666 | } 1667 | animationCompleted(); 1668 | }, 1669 | 1670 | removeClass : function(element, className, animationCompleted) { 1671 | return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted); 1672 | } 1673 | }; 1674 | 1675 | function suffixClasses(classes, suffix) { 1676 | var className = ''; 1677 | classes = angular.isArray(classes) ? classes : classes.split(/\s+/); 1678 | forEach(classes, function(klass, i) { 1679 | if(klass && klass.length > 0) { 1680 | className += (i > 0 ? ' ' : '') + klass + suffix; 1681 | } 1682 | }); 1683 | return className; 1684 | } 1685 | }]); 1686 | }]); 1687 | 1688 | 1689 | })(window, window.angular); 1690 | -------------------------------------------------------------------------------- /src/Scripts/angular-route.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.26 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /** 9 | * @ngdoc module 10 | * @name ngRoute 11 | * @description 12 | * 13 | * # ngRoute 14 | * 15 | * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. 16 | * 17 | * ## Example 18 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 19 | * 20 | * 21 | *
22 | */ 23 | /* global -ngRouteModule */ 24 | var ngRouteModule = angular.module('ngRoute', ['ng']). 25 | provider('$route', $RouteProvider); 26 | 27 | /** 28 | * @ngdoc provider 29 | * @name $routeProvider 30 | * @kind function 31 | * 32 | * @description 33 | * 34 | * Used for configuring routes. 35 | * 36 | * ## Example 37 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 38 | * 39 | * ## Dependencies 40 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 41 | */ 42 | function $RouteProvider(){ 43 | function inherit(parent, extra) { 44 | return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); 45 | } 46 | 47 | var routes = {}; 48 | 49 | /** 50 | * @ngdoc method 51 | * @name $routeProvider#when 52 | * 53 | * @param {string} path Route path (matched against `$location.path`). If `$location.path` 54 | * contains redundant trailing slash or is missing one, the route will still match and the 55 | * `$location.path` will be updated to add or drop the trailing slash to exactly match the 56 | * route definition. 57 | * 58 | * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up 59 | * to the next slash are matched and stored in `$routeParams` under the given `name` 60 | * when the route matches. 61 | * * `path` can contain named groups starting with a colon and ending with a star: 62 | * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` 63 | * when the route matches. 64 | * * `path` can contain optional named groups with a question mark: e.g.`:name?`. 65 | * 66 | * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 67 | * `/color/brown/largecode/code/with/slashes/edit` and extract: 68 | * 69 | * * `color: brown` 70 | * * `largecode: code/with/slashes`. 71 | * 72 | * 73 | * @param {Object} route Mapping information to be assigned to `$route.current` on route 74 | * match. 75 | * 76 | * Object properties: 77 | * 78 | * - `controller` – `{(string|function()=}` – Controller fn that should be associated with 79 | * newly created scope or the name of a {@link angular.Module#controller registered 80 | * controller} if passed as a string. 81 | * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be 82 | * published to scope under the `controllerAs` name. 83 | * - `template` – `{string=|function()=}` – html template as a string or a function that 84 | * returns an html template as a string which should be used by {@link 85 | * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 86 | * This property takes precedence over `templateUrl`. 87 | * 88 | * If `template` is a function, it will be called with the following parameters: 89 | * 90 | * - `{Array.}` - route parameters extracted from the current 91 | * `$location.path()` by applying the current route 92 | * 93 | * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 94 | * template that should be used by {@link ngRoute.directive:ngView ngView}. 95 | * 96 | * If `templateUrl` is a function, it will be called with the following parameters: 97 | * 98 | * - `{Array.}` - route parameters extracted from the current 99 | * `$location.path()` by applying the current route 100 | * 101 | * - `resolve` - `{Object.=}` - An optional map of dependencies which should 102 | * be injected into the controller. If any of these dependencies are promises, the router 103 | * will wait for them all to be resolved or one to be rejected before the controller is 104 | * instantiated. 105 | * If all the promises are resolved successfully, the values of the resolved promises are 106 | * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is 107 | * fired. If any of the promises are rejected the 108 | * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object 109 | * is: 110 | * 111 | * - `key` – `{string}`: a name of a dependency to be injected into the controller. 112 | * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 113 | * Otherwise if function, then it is {@link auto.$injector#invoke injected} 114 | * and the return value is treated as the dependency. If the result is a promise, it is 115 | * resolved before its value is injected into the controller. Be aware that 116 | * `ngRoute.$routeParams` will still refer to the previous route within these resolve 117 | * functions. Use `$route.current.params` to access the new route parameters, instead. 118 | * 119 | * - `redirectTo` – {(string|function())=} – value to update 120 | * {@link ng.$location $location} path with and trigger route redirection. 121 | * 122 | * If `redirectTo` is a function, it will be called with the following parameters: 123 | * 124 | * - `{Object.}` - route parameters extracted from the current 125 | * `$location.path()` by applying the current route templateUrl. 126 | * - `{string}` - current `$location.path()` 127 | * - `{Object}` - current `$location.search()` 128 | * 129 | * The custom `redirectTo` function is expected to return a string which will be used 130 | * to update `$location.path()` and `$location.search()`. 131 | * 132 | * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` 133 | * or `$location.hash()` changes. 134 | * 135 | * If the option is set to `false` and url in the browser changes, then 136 | * `$routeUpdate` event is broadcasted on the root scope. 137 | * 138 | * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 139 | * 140 | * If the option is set to `true`, then the particular route can be matched without being 141 | * case sensitive 142 | * 143 | * @returns {Object} self 144 | * 145 | * @description 146 | * Adds a new route definition to the `$route` service. 147 | */ 148 | this.when = function(path, route) { 149 | routes[path] = angular.extend( 150 | {reloadOnSearch: true}, 151 | route, 152 | path && pathRegExp(path, route) 153 | ); 154 | 155 | // create redirection for trailing slashes 156 | if (path) { 157 | var redirectPath = (path[path.length-1] == '/') 158 | ? path.substr(0, path.length-1) 159 | : path +'/'; 160 | 161 | routes[redirectPath] = angular.extend( 162 | {redirectTo: path}, 163 | pathRegExp(redirectPath, route) 164 | ); 165 | } 166 | 167 | return this; 168 | }; 169 | 170 | /** 171 | * @param path {string} path 172 | * @param opts {Object} options 173 | * @return {?Object} 174 | * 175 | * @description 176 | * Normalizes the given path, returning a regular expression 177 | * and the original path. 178 | * 179 | * Inspired by pathRexp in visionmedia/express/lib/utils.js. 180 | */ 181 | function pathRegExp(path, opts) { 182 | var insensitive = opts.caseInsensitiveMatch, 183 | ret = { 184 | originalPath: path, 185 | regexp: path 186 | }, 187 | keys = ret.keys = []; 188 | 189 | path = path 190 | .replace(/([().])/g, '\\$1') 191 | .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){ 192 | var optional = option === '?' ? option : null; 193 | var star = option === '*' ? option : null; 194 | keys.push({ name: key, optional: !!optional }); 195 | slash = slash || ''; 196 | return '' 197 | + (optional ? '' : slash) 198 | + '(?:' 199 | + (optional ? slash : '') 200 | + (star && '(.+?)' || '([^/]+)') 201 | + (optional || '') 202 | + ')' 203 | + (optional || ''); 204 | }) 205 | .replace(/([\/$\*])/g, '\\$1'); 206 | 207 | ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 208 | return ret; 209 | } 210 | 211 | /** 212 | * @ngdoc method 213 | * @name $routeProvider#otherwise 214 | * 215 | * @description 216 | * Sets route definition that will be used on route change when no other route definition 217 | * is matched. 218 | * 219 | * @param {Object} params Mapping information to be assigned to `$route.current`. 220 | * @returns {Object} self 221 | */ 222 | this.otherwise = function(params) { 223 | this.when(null, params); 224 | return this; 225 | }; 226 | 227 | 228 | this.$get = ['$rootScope', 229 | '$location', 230 | '$routeParams', 231 | '$q', 232 | '$injector', 233 | '$http', 234 | '$templateCache', 235 | '$sce', 236 | function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { 237 | 238 | /** 239 | * @ngdoc service 240 | * @name $route 241 | * @requires $location 242 | * @requires $routeParams 243 | * 244 | * @property {Object} current Reference to the current route definition. 245 | * The route definition contains: 246 | * 247 | * - `controller`: The controller constructor as define in route definition. 248 | * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 249 | * controller instantiation. The `locals` contain 250 | * the resolved values of the `resolve` map. Additionally the `locals` also contain: 251 | * 252 | * - `$scope` - The current route scope. 253 | * - `$template` - The current route template HTML. 254 | * 255 | * @property {Object} routes Object with all route configuration Objects as its properties. 256 | * 257 | * @description 258 | * `$route` is used for deep-linking URLs to controllers and views (HTML partials). 259 | * It watches `$location.url()` and tries to map the path to an existing route definition. 260 | * 261 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 262 | * 263 | * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 264 | * 265 | * The `$route` service is typically used in conjunction with the 266 | * {@link ngRoute.directive:ngView `ngView`} directive and the 267 | * {@link ngRoute.$routeParams `$routeParams`} service. 268 | * 269 | * @example 270 | * This example shows how changing the URL hash causes the `$route` to match a route against the 271 | * URL, and the `ngView` pulls in the partial. 272 | * 273 | * 275 | * 276 | *
277 | * Choose: 278 | * Moby | 279 | * Moby: Ch1 | 280 | * Gatsby | 281 | * Gatsby: Ch4 | 282 | * Scarlet Letter
283 | * 284 | *
285 | * 286 | *
287 | * 288 | *
$location.path() = {{$location.path()}}
289 | *
$route.current.templateUrl = {{$route.current.templateUrl}}
290 | *
$route.current.params = {{$route.current.params}}
291 | *
$route.current.scope.name = {{$route.current.scope.name}}
292 | *
$routeParams = {{$routeParams}}
293 | *
294 | *
295 | * 296 | * 297 | * controller: {{name}}
298 | * Book Id: {{params.bookId}}
299 | *
300 | * 301 | * 302 | * controller: {{name}}
303 | * Book Id: {{params.bookId}}
304 | * Chapter Id: {{params.chapterId}} 305 | *
306 | * 307 | * 308 | * angular.module('ngRouteExample', ['ngRoute']) 309 | * 310 | * .controller('MainController', function($scope, $route, $routeParams, $location) { 311 | * $scope.$route = $route; 312 | * $scope.$location = $location; 313 | * $scope.$routeParams = $routeParams; 314 | * }) 315 | * 316 | * .controller('BookController', function($scope, $routeParams) { 317 | * $scope.name = "BookController"; 318 | * $scope.params = $routeParams; 319 | * }) 320 | * 321 | * .controller('ChapterController', function($scope, $routeParams) { 322 | * $scope.name = "ChapterController"; 323 | * $scope.params = $routeParams; 324 | * }) 325 | * 326 | * .config(function($routeProvider, $locationProvider) { 327 | * $routeProvider 328 | * .when('/Book/:bookId', { 329 | * templateUrl: 'book.html', 330 | * controller: 'BookController', 331 | * resolve: { 332 | * // I will cause a 1 second delay 333 | * delay: function($q, $timeout) { 334 | * var delay = $q.defer(); 335 | * $timeout(delay.resolve, 1000); 336 | * return delay.promise; 337 | * } 338 | * } 339 | * }) 340 | * .when('/Book/:bookId/ch/:chapterId', { 341 | * templateUrl: 'chapter.html', 342 | * controller: 'ChapterController' 343 | * }); 344 | * 345 | * // configure html5 to get links working on jsfiddle 346 | * $locationProvider.html5Mode(true); 347 | * }); 348 | * 349 | * 350 | * 351 | * 352 | * it('should load and compile correct template', function() { 353 | * element(by.linkText('Moby: Ch1')).click(); 354 | * var content = element(by.css('[ng-view]')).getText(); 355 | * expect(content).toMatch(/controller\: ChapterController/); 356 | * expect(content).toMatch(/Book Id\: Moby/); 357 | * expect(content).toMatch(/Chapter Id\: 1/); 358 | * 359 | * element(by.partialLinkText('Scarlet')).click(); 360 | * 361 | * content = element(by.css('[ng-view]')).getText(); 362 | * expect(content).toMatch(/controller\: BookController/); 363 | * expect(content).toMatch(/Book Id\: Scarlet/); 364 | * }); 365 | * 366 | *
367 | */ 368 | 369 | /** 370 | * @ngdoc event 371 | * @name $route#$routeChangeStart 372 | * @eventType broadcast on root scope 373 | * @description 374 | * Broadcasted before a route change. At this point the route services starts 375 | * resolving all of the dependencies needed for the route change to occur. 376 | * Typically this involves fetching the view template as well as any dependencies 377 | * defined in `resolve` route property. Once all of the dependencies are resolved 378 | * `$routeChangeSuccess` is fired. 379 | * 380 | * @param {Object} angularEvent Synthetic event object. 381 | * @param {Route} next Future route information. 382 | * @param {Route} current Current route information. 383 | */ 384 | 385 | /** 386 | * @ngdoc event 387 | * @name $route#$routeChangeSuccess 388 | * @eventType broadcast on root scope 389 | * @description 390 | * Broadcasted after a route dependencies are resolved. 391 | * {@link ngRoute.directive:ngView ngView} listens for the directive 392 | * to instantiate the controller and render the view. 393 | * 394 | * @param {Object} angularEvent Synthetic event object. 395 | * @param {Route} current Current route information. 396 | * @param {Route|Undefined} previous Previous route information, or undefined if current is 397 | * first route entered. 398 | */ 399 | 400 | /** 401 | * @ngdoc event 402 | * @name $route#$routeChangeError 403 | * @eventType broadcast on root scope 404 | * @description 405 | * Broadcasted if any of the resolve promises are rejected. 406 | * 407 | * @param {Object} angularEvent Synthetic event object 408 | * @param {Route} current Current route information. 409 | * @param {Route} previous Previous route information. 410 | * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 411 | */ 412 | 413 | /** 414 | * @ngdoc event 415 | * @name $route#$routeUpdate 416 | * @eventType broadcast on root scope 417 | * @description 418 | * 419 | * The `reloadOnSearch` property has been set to false, and we are reusing the same 420 | * instance of the Controller. 421 | */ 422 | 423 | var forceReload = false, 424 | $route = { 425 | routes: routes, 426 | 427 | /** 428 | * @ngdoc method 429 | * @name $route#reload 430 | * 431 | * @description 432 | * Causes `$route` service to reload the current route even if 433 | * {@link ng.$location $location} hasn't changed. 434 | * 435 | * As a result of that, {@link ngRoute.directive:ngView ngView} 436 | * creates new scope, reinstantiates the controller. 437 | */ 438 | reload: function() { 439 | forceReload = true; 440 | $rootScope.$evalAsync(updateRoute); 441 | } 442 | }; 443 | 444 | $rootScope.$on('$locationChangeSuccess', updateRoute); 445 | 446 | return $route; 447 | 448 | ///////////////////////////////////////////////////// 449 | 450 | /** 451 | * @param on {string} current url 452 | * @param route {Object} route regexp to match the url against 453 | * @return {?Object} 454 | * 455 | * @description 456 | * Check if the route matches the current url. 457 | * 458 | * Inspired by match in 459 | * visionmedia/express/lib/router/router.js. 460 | */ 461 | function switchRouteMatcher(on, route) { 462 | var keys = route.keys, 463 | params = {}; 464 | 465 | if (!route.regexp) return null; 466 | 467 | var m = route.regexp.exec(on); 468 | if (!m) return null; 469 | 470 | for (var i = 1, len = m.length; i < len; ++i) { 471 | var key = keys[i - 1]; 472 | 473 | var val = m[i]; 474 | 475 | if (key && val) { 476 | params[key.name] = val; 477 | } 478 | } 479 | return params; 480 | } 481 | 482 | function updateRoute() { 483 | var next = parseRoute(), 484 | last = $route.current; 485 | 486 | if (next && last && next.$$route === last.$$route 487 | && angular.equals(next.pathParams, last.pathParams) 488 | && !next.reloadOnSearch && !forceReload) { 489 | last.params = next.params; 490 | angular.copy(last.params, $routeParams); 491 | $rootScope.$broadcast('$routeUpdate', last); 492 | } else if (next || last) { 493 | forceReload = false; 494 | $rootScope.$broadcast('$routeChangeStart', next, last); 495 | $route.current = next; 496 | if (next) { 497 | if (next.redirectTo) { 498 | if (angular.isString(next.redirectTo)) { 499 | $location.path(interpolate(next.redirectTo, next.params)).search(next.params) 500 | .replace(); 501 | } else { 502 | $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) 503 | .replace(); 504 | } 505 | } 506 | } 507 | 508 | $q.when(next). 509 | then(function() { 510 | if (next) { 511 | var locals = angular.extend({}, next.resolve), 512 | template, templateUrl; 513 | 514 | angular.forEach(locals, function(value, key) { 515 | locals[key] = angular.isString(value) ? 516 | $injector.get(value) : $injector.invoke(value); 517 | }); 518 | 519 | if (angular.isDefined(template = next.template)) { 520 | if (angular.isFunction(template)) { 521 | template = template(next.params); 522 | } 523 | } else if (angular.isDefined(templateUrl = next.templateUrl)) { 524 | if (angular.isFunction(templateUrl)) { 525 | templateUrl = templateUrl(next.params); 526 | } 527 | templateUrl = $sce.getTrustedResourceUrl(templateUrl); 528 | if (angular.isDefined(templateUrl)) { 529 | next.loadedTemplateUrl = templateUrl; 530 | template = $http.get(templateUrl, {cache: $templateCache}). 531 | then(function(response) { return response.data; }); 532 | } 533 | } 534 | if (angular.isDefined(template)) { 535 | locals['$template'] = template; 536 | } 537 | return $q.all(locals); 538 | } 539 | }). 540 | // after route change 541 | then(function(locals) { 542 | if (next == $route.current) { 543 | if (next) { 544 | next.locals = locals; 545 | angular.copy(next.params, $routeParams); 546 | } 547 | $rootScope.$broadcast('$routeChangeSuccess', next, last); 548 | } 549 | }, function(error) { 550 | if (next == $route.current) { 551 | $rootScope.$broadcast('$routeChangeError', next, last, error); 552 | } 553 | }); 554 | } 555 | } 556 | 557 | 558 | /** 559 | * @returns {Object} the current active route, by matching it against the URL 560 | */ 561 | function parseRoute() { 562 | // Match a route 563 | var params, match; 564 | angular.forEach(routes, function(route, path) { 565 | if (!match && (params = switchRouteMatcher($location.path(), route))) { 566 | match = inherit(route, { 567 | params: angular.extend({}, $location.search(), params), 568 | pathParams: params}); 569 | match.$$route = route; 570 | } 571 | }); 572 | // No route matched; fallback to "otherwise" route 573 | return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 574 | } 575 | 576 | /** 577 | * @returns {string} interpolation of the redirect path with the parameters 578 | */ 579 | function interpolate(string, params) { 580 | var result = []; 581 | angular.forEach((string||'').split(':'), function(segment, i) { 582 | if (i === 0) { 583 | result.push(segment); 584 | } else { 585 | var segmentMatch = segment.match(/(\w+)(.*)/); 586 | var key = segmentMatch[1]; 587 | result.push(params[key]); 588 | result.push(segmentMatch[2] || ''); 589 | delete params[key]; 590 | } 591 | }); 592 | return result.join(''); 593 | } 594 | }]; 595 | } 596 | 597 | ngRouteModule.provider('$routeParams', $RouteParamsProvider); 598 | 599 | 600 | /** 601 | * @ngdoc service 602 | * @name $routeParams 603 | * @requires $route 604 | * 605 | * @description 606 | * The `$routeParams` service allows you to retrieve the current set of route parameters. 607 | * 608 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 609 | * 610 | * The route parameters are a combination of {@link ng.$location `$location`}'s 611 | * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. 612 | * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. 613 | * 614 | * In case of parameter name collision, `path` params take precedence over `search` params. 615 | * 616 | * The service guarantees that the identity of the `$routeParams` object will remain unchanged 617 | * (but its properties will likely change) even when a route change occurs. 618 | * 619 | * Note that the `$routeParams` are only updated *after* a route change completes successfully. 620 | * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 621 | * Instead you can use `$route.current.params` to access the new route's parameters. 622 | * 623 | * @example 624 | * ```js 625 | * // Given: 626 | * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby 627 | * // Route: /Chapter/:chapterId/Section/:sectionId 628 | * // 629 | * // Then 630 | * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} 631 | * ``` 632 | */ 633 | function $RouteParamsProvider() { 634 | this.$get = function() { return {}; }; 635 | } 636 | 637 | ngRouteModule.directive('ngView', ngViewFactory); 638 | ngRouteModule.directive('ngView', ngViewFillContentFactory); 639 | 640 | 641 | /** 642 | * @ngdoc directive 643 | * @name ngView 644 | * @restrict ECA 645 | * 646 | * @description 647 | * # Overview 648 | * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 649 | * including the rendered template of the current route into the main layout (`index.html`) file. 650 | * Every time the current route changes, the included view changes with it according to the 651 | * configuration of the `$route` service. 652 | * 653 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 654 | * 655 | * @animations 656 | * enter - animation is used to bring new content into the browser. 657 | * leave - animation is used to animate existing content away. 658 | * 659 | * The enter and leave animation occur concurrently. 660 | * 661 | * @scope 662 | * @priority 400 663 | * @param {string=} onload Expression to evaluate whenever the view updates. 664 | * 665 | * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll 666 | * $anchorScroll} to scroll the viewport after the view is updated. 667 | * 668 | * - If the attribute is not set, disable scrolling. 669 | * - If the attribute is set without value, enable scrolling. 670 | * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated 671 | * as an expression yields a truthy value. 672 | * @example 673 | 676 | 677 |
678 | Choose: 679 | Moby | 680 | Moby: Ch1 | 681 | Gatsby | 682 | Gatsby: Ch4 | 683 | Scarlet Letter
684 | 685 |
686 |
687 |
688 |
689 | 690 |
$location.path() = {{main.$location.path()}}
691 |
$route.current.templateUrl = {{main.$route.current.templateUrl}}
692 |
$route.current.params = {{main.$route.current.params}}
693 |
$route.current.scope.name = {{main.$route.current.scope.name}}
694 |
$routeParams = {{main.$routeParams}}
695 |
696 |
697 | 698 | 699 |
700 | controller: {{book.name}}
701 | Book Id: {{book.params.bookId}}
702 |
703 |
704 | 705 | 706 |
707 | controller: {{chapter.name}}
708 | Book Id: {{chapter.params.bookId}}
709 | Chapter Id: {{chapter.params.chapterId}} 710 |
711 |
712 | 713 | 714 | .view-animate-container { 715 | position:relative; 716 | height:100px!important; 717 | position:relative; 718 | background:white; 719 | border:1px solid black; 720 | height:40px; 721 | overflow:hidden; 722 | } 723 | 724 | .view-animate { 725 | padding:10px; 726 | } 727 | 728 | .view-animate.ng-enter, .view-animate.ng-leave { 729 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 730 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 731 | 732 | display:block; 733 | width:100%; 734 | border-left:1px solid black; 735 | 736 | position:absolute; 737 | top:0; 738 | left:0; 739 | right:0; 740 | bottom:0; 741 | padding:10px; 742 | } 743 | 744 | .view-animate.ng-enter { 745 | left:100%; 746 | } 747 | .view-animate.ng-enter.ng-enter-active { 748 | left:0; 749 | } 750 | .view-animate.ng-leave.ng-leave-active { 751 | left:-100%; 752 | } 753 | 754 | 755 | 756 | angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) 757 | .config(['$routeProvider', '$locationProvider', 758 | function($routeProvider, $locationProvider) { 759 | $routeProvider 760 | .when('/Book/:bookId', { 761 | templateUrl: 'book.html', 762 | controller: 'BookCtrl', 763 | controllerAs: 'book' 764 | }) 765 | .when('/Book/:bookId/ch/:chapterId', { 766 | templateUrl: 'chapter.html', 767 | controller: 'ChapterCtrl', 768 | controllerAs: 'chapter' 769 | }); 770 | 771 | $locationProvider.html5Mode(true); 772 | }]) 773 | .controller('MainCtrl', ['$route', '$routeParams', '$location', 774 | function($route, $routeParams, $location) { 775 | this.$route = $route; 776 | this.$location = $location; 777 | this.$routeParams = $routeParams; 778 | }]) 779 | .controller('BookCtrl', ['$routeParams', function($routeParams) { 780 | this.name = "BookCtrl"; 781 | this.params = $routeParams; 782 | }]) 783 | .controller('ChapterCtrl', ['$routeParams', function($routeParams) { 784 | this.name = "ChapterCtrl"; 785 | this.params = $routeParams; 786 | }]); 787 | 788 | 789 | 790 | 791 | it('should load and compile correct template', function() { 792 | element(by.linkText('Moby: Ch1')).click(); 793 | var content = element(by.css('[ng-view]')).getText(); 794 | expect(content).toMatch(/controller\: ChapterCtrl/); 795 | expect(content).toMatch(/Book Id\: Moby/); 796 | expect(content).toMatch(/Chapter Id\: 1/); 797 | 798 | element(by.partialLinkText('Scarlet')).click(); 799 | 800 | content = element(by.css('[ng-view]')).getText(); 801 | expect(content).toMatch(/controller\: BookCtrl/); 802 | expect(content).toMatch(/Book Id\: Scarlet/); 803 | }); 804 | 805 |
806 | */ 807 | 808 | 809 | /** 810 | * @ngdoc event 811 | * @name ngView#$viewContentLoaded 812 | * @eventType emit on the current ngView scope 813 | * @description 814 | * Emitted every time the ngView content is reloaded. 815 | */ 816 | ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; 817 | function ngViewFactory( $route, $anchorScroll, $animate) { 818 | return { 819 | restrict: 'ECA', 820 | terminal: true, 821 | priority: 400, 822 | transclude: 'element', 823 | link: function(scope, $element, attr, ctrl, $transclude) { 824 | var currentScope, 825 | currentElement, 826 | previousElement, 827 | autoScrollExp = attr.autoscroll, 828 | onloadExp = attr.onload || ''; 829 | 830 | scope.$on('$routeChangeSuccess', update); 831 | update(); 832 | 833 | function cleanupLastView() { 834 | if(previousElement) { 835 | previousElement.remove(); 836 | previousElement = null; 837 | } 838 | if(currentScope) { 839 | currentScope.$destroy(); 840 | currentScope = null; 841 | } 842 | if(currentElement) { 843 | $animate.leave(currentElement, function() { 844 | previousElement = null; 845 | }); 846 | previousElement = currentElement; 847 | currentElement = null; 848 | } 849 | } 850 | 851 | function update() { 852 | var locals = $route.current && $route.current.locals, 853 | template = locals && locals.$template; 854 | 855 | if (angular.isDefined(template)) { 856 | var newScope = scope.$new(); 857 | var current = $route.current; 858 | 859 | // Note: This will also link all children of ng-view that were contained in the original 860 | // html. If that content contains controllers, ... they could pollute/change the scope. 861 | // However, using ng-view on an element with additional content does not make sense... 862 | // Note: We can't remove them in the cloneAttchFn of $transclude as that 863 | // function is called before linking the content, which would apply child 864 | // directives to non existing elements. 865 | var clone = $transclude(newScope, function(clone) { 866 | $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { 867 | if (angular.isDefined(autoScrollExp) 868 | && (!autoScrollExp || scope.$eval(autoScrollExp))) { 869 | $anchorScroll(); 870 | } 871 | }); 872 | cleanupLastView(); 873 | }); 874 | 875 | currentElement = clone; 876 | currentScope = current.scope = newScope; 877 | currentScope.$emit('$viewContentLoaded'); 878 | currentScope.$eval(onloadExp); 879 | } else { 880 | cleanupLastView(); 881 | } 882 | } 883 | } 884 | }; 885 | } 886 | 887 | // This directive is called during the $transclude call of the first `ngView` directive. 888 | // It will replace and compile the content of the element with the loaded template. 889 | // We need this directive so that the element content is already filled when 890 | // the link function of another directive on the same element as ngView 891 | // is called. 892 | ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; 893 | function ngViewFillContentFactory($compile, $controller, $route) { 894 | return { 895 | restrict: 'ECA', 896 | priority: -400, 897 | link: function(scope, $element) { 898 | var current = $route.current, 899 | locals = current.locals; 900 | 901 | $element.html(locals.$template); 902 | 903 | var link = $compile($element.contents()); 904 | 905 | if (current.controller) { 906 | locals.$scope = scope; 907 | var controller = $controller(current.controller, locals); 908 | if (current.controllerAs) { 909 | scope[current.controllerAs] = controller; 910 | } 911 | $element.data('$ngControllerController', controller); 912 | $element.children().data('$ngControllerController', controller); 913 | } 914 | 915 | link(scope); 916 | } 917 | }; 918 | } 919 | 920 | 921 | })(window, window.angular); 922 | -------------------------------------------------------------------------------- /src/Scripts/angular.intellisense.js: -------------------------------------------------------------------------------- 1 | (function (intellisense) { 2 | // If AngularJS is undefined, then bypass AngularJS Intellisense. 3 | if (!angular) { 4 | return; 5 | } 6 | 7 | // The following log levels are supported by AngularJS Intellisense. 8 | var LOG_LEVEL = { 9 | VERBOSE: 1, 10 | INFO: 2, 11 | WARN: 3, 12 | ERROR: 4, 13 | OFF: 5 14 | }; 15 | 16 | // Set the current log level to one of the log levels above in order to view log messages. 17 | var CURRENT_LOG_LEVEL = LOG_LEVEL.OFF; 18 | 19 | // Expose a hidden object that may be used to set the log level from any file. 20 | window._$AngularJS_VisualStudio_Intellisense = { 21 | setLogLevelVerbose: function () { CURRENT_LOG_LEVEL = LOG_LEVEL.VERBOSE; }, 22 | setLogLevelInfo: function () { CURRENT_LOG_LEVEL = LOG_LEVEL.INFO; }, 23 | setLogLevelWarn: function () { CURRENT_LOG_LEVEL = LOG_LEVEL.WARN; }, 24 | setLogLevelError: function () { CURRENT_LOG_LEVEL = LOG_LEVEL.ERROR; }, 25 | setLogLevelOff: function () { CURRENT_LOG_LEVEL = LOG_LEVEL.OFF; } 26 | }; 27 | 28 | //#region Logging Functions 29 | 30 | function indent(level) { 31 | var pad = ' '; // Two-space pad. 32 | 33 | return Array(level + 1).join(pad); 34 | } 35 | 36 | function logMessage(logLevel, message) { 37 | if (CURRENT_LOG_LEVEL > logLevel) { 38 | return; 39 | } else { 40 | intellisense.logMessage(message); 41 | } 42 | } 43 | 44 | function logValue(logLevel, value, key, level) { 45 | if (CURRENT_LOG_LEVEL > logLevel) { 46 | return; 47 | } 48 | 49 | if (angular.isUndefined(value)) { 50 | value = 'undefined'; 51 | } 52 | 53 | if (angular.isUndefined(key)) { 54 | key = ''; 55 | } 56 | 57 | if (angular.isUndefined(level)) { 58 | level = 0; 59 | } 60 | 61 | var pad = indent(level); 62 | 63 | if (angular.isString(value)) { 64 | value = '"' + value + '"'; 65 | } else if (angular.isFunction(value)) { 66 | value = '$FUNCTION'; 67 | } else if (angular.isElement(value)) { 68 | value = '$ELEMENT'; 69 | } else if (isWindow(value)) { 70 | value = '$WINDOW'; 71 | } else if (value && document === value) { 72 | value = '$DOCUMENT'; 73 | } else if (isScope(value)) { 74 | value = '$SCOPE'; 75 | } 76 | if (angular.isArray(value)) { 77 | intellisense.logMessage(pad + (key ? key + ': ' : '') + ' ['); 78 | forEach(value, function (item) { 79 | logValue(logLevel, item, '', level + 1); 80 | }); 81 | 82 | intellisense.logMessage(pad + ']' + (level > 0 ? ',' : '')); 83 | } else if (angular.isObject(value)) { 84 | if (filter(value)) { 85 | intellisense.logMessage(pad + (key ? key + ': ' : '') + '{'); 86 | 87 | forEach(value, function (propertyValue, key) { 88 | logValue(logLevel, propertyValue, key, level + 1); 89 | }); 90 | 91 | intellisense.logMessage(pad + '}' + (level > 0 ? ',' : '')); 92 | } 93 | } else { 94 | intellisense.logMessage(pad + (key ? key + ': ' : '') + value + (level > 0 ? ',' : '')); 95 | } 96 | } 97 | 98 | //#endregion 99 | 100 | //#region Utility Functions 101 | 102 | function forEach(obj, iterator, context) { 103 | var key; 104 | if (obj.forEach && obj.forEach !== forEach) { 105 | obj.forEach(iterator, context); 106 | } else if (angular.isArray(obj)) { 107 | for (key = 0; key < obj.length; key++) { 108 | iterator.call(context, obj[key], key); 109 | } 110 | } else { 111 | for (key in obj) { 112 | iterator.call(context, obj[key], key); 113 | } 114 | } 115 | 116 | return obj; 117 | } 118 | 119 | // Copy AngularJS functions for determining window or $scope objects. 120 | function isWindow(obj) { 121 | return obj && obj.document && obj.location && obj.alert && obj.setInterval; 122 | } 123 | 124 | function isScope(obj) { 125 | return obj && obj.$evalAsync && obj.$watch; 126 | } 127 | 128 | //#endregion 129 | 130 | //#region $compile.directive.Attributes 131 | 132 | // HACK: (JMB) Since the directive attributes type is hidden and "difficult" to access during Intellisense generation, 133 | // (i.e. I couldn't figure it out), create a copy of it here so that Intellisense treats it correctly. 134 | var Attributes = function (element, attr) { 135 | this.$$element = element; 136 | this.$attr = attr || {}; 137 | }; 138 | 139 | Attributes.prototype = { 140 | $normalize: function (name) { 141 | return ''; 142 | }, 143 | 144 | 145 | /** 146 | * @ngdoc method 147 | * @name $compile.directive.Attributes#$addClass 148 | * @kind function 149 | * 150 | * @description 151 | * Adds the CSS class value specified by the classVal parameter to the element. If animations 152 | * are enabled then an animation will be triggered for the class addition. 153 | * 154 | * @param {string} classVal The className value that will be added to the element 155 | */ 156 | $addClass: function (classVal) { 157 | }, 158 | 159 | /** 160 | * @ngdoc method 161 | * @name $compile.directive.Attributes#$removeClass 162 | * @kind function 163 | * 164 | * @description 165 | * Removes the CSS class value specified by the classVal parameter from the element. If 166 | * animations are enabled then an animation will be triggered for the class removal. 167 | * 168 | * @param {string} classVal The className value that will be removed from the element 169 | */ 170 | $removeClass: function (classVal) { 171 | }, 172 | 173 | /** 174 | * @ngdoc method 175 | * @name $compile.directive.Attributes#$updateClass 176 | * @kind function 177 | * 178 | * @description 179 | * Adds and removes the appropriate CSS class values to the element based on the difference 180 | * between the new and old CSS class values (specified as newClasses and oldClasses). 181 | * 182 | * @param {string} newClasses The current CSS className value 183 | * @param {string} oldClasses The former CSS className value 184 | */ 185 | $updateClass: function (newClasses, oldClasses) { 186 | }, 187 | 188 | /** 189 | * Set a normalized attribute on the element in a way such that all directives 190 | * can share the attribute. This function properly handles boolean attributes. 191 | * @param {string} key Normalized key. (ie ngAttribute) 192 | * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 193 | * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 194 | * Defaults to true. 195 | * @param {string=} attrName Optional none normalized name. Defaults to key. 196 | */ 197 | $set: function (key, value, writeAttr, attrName) { 198 | }, 199 | 200 | 201 | /** 202 | * @ngdoc method 203 | * @name $compile.directive.Attributes#$observe 204 | * @kind function 205 | * 206 | * @description 207 | * Observes an interpolated attribute. 208 | * 209 | * The observer function will be invoked once during the next `$digest` following 210 | * compilation. The observer is then invoked whenever the interpolated value 211 | * changes. 212 | * 213 | * @param {string} key Normalized key. (ie ngAttribute) . 214 | * @param {function(interpolatedValue)} fn Function that will be called whenever 215 | the interpolated value of the attribute changes. 216 | * See the {@link guide/directive#Attributes Directives} guide for more info. 217 | * @returns {function()} the `fn` parameter. 218 | */ 219 | $observe: function (key, fn) { 220 | return fn; 221 | } 222 | }; 223 | 224 | //#endregion 225 | 226 | //#region Module Tracking 227 | 228 | // Keep track of module names, with each module name mapped to an array of all its required modules. 229 | var requiredModuleMap = {}; 230 | var moduleProviderFunctions = ['provider', 'factory', 'service', 'animation', 'filter', 'controller', 'directive', 'config', 'run']; 231 | 232 | // Keep track of the provider injector in order to inject into providers. 233 | var providerInjector; 234 | 235 | angular.module('ng').config(['$injector', '$provide', function ($injector, $provide) { 236 | // Keep track of the the provider injector. 237 | providerInjector = $injector; 238 | 239 | $provide.decorator("$http", ['$delegate', function ($delegate) { 240 | /** 241 | * @typedef {Object} httpConfig 242 | * @property {String} method HTTP method (e.g. 'GET', 'POST', etc) 243 | * @property {String} url Absolute or relative URL of the resource that is being requested. 244 | * @property {Object.} params – Map of strings or objects which will be turned 245 | * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 246 | * JSONified. 247 | * @property {string|Object} data – Data to be sent as the request message data. 248 | * @property {Object} headers – Map of strings or functions which return strings representing 249 | * HTTP headers to send to the server. If the return value of a function is null, the 250 | * header will not be sent. 251 | * @property {string} xsrfHeaderName – Name of HTTP header to populate with the XSRF token. 252 | * @property {string} xsrfCookieName – Name of cookie containing the XSRF token. 253 | * @property {function(data, headersGetter)|Array.} transformRequest – 254 | * transform function or an array of such functions. The transform function takes the http 255 | * request body and headers and returns its transformed (typically serialized) version. 256 | * @property {function(data, headersGetter)|Array.} transformResponse – 257 | * transform function or an array of such functions. The transform function takes the http 258 | * response body and headers and returns its transformed (typically deserialized) version. 259 | * @property {boolean|Cache} cache – If true, a default $http cache will be used to cache the 260 | * GET request, otherwise if a cache instance built with 261 | * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 262 | * caching. 263 | * @property {number|Promise} timeout – timeout in milliseconds, or {@link ng.$q promise} 264 | * that should abort the request when resolved. 265 | * @property {boolean} withCredentials - whether to set the `withCredentials` flag on the 266 | * XHR object. 267 | * @property {string} responseType 268 | */ 269 | 270 | /** 271 | * @param {string} url 272 | * @param {httpConfig} [config] 273 | */ 274 | function httpGetDocs(url, config) { 275 | } 276 | /** 277 | * @param {string} url 278 | * @param {httpConfig} [config] 279 | */ 280 | function httpHeadDocs(url, config) { } 281 | /** 282 | * @param {string} url 283 | * @param {httpConfig} [config] 284 | */ 285 | function httpJsonpDocs(url, config) { } 286 | /** 287 | * @param {string} url 288 | * @param {httpConfig} [config] 289 | */ 290 | function httpDeleteDocs(url, config) { } 291 | /** 292 | * @param {string} url 293 | * @param {*} data 294 | * @param {httpConfig} [config] 295 | */ 296 | function httpPostDocs(url, data, config) { } 297 | /** 298 | * @param {string} url 299 | * @param {*} data 300 | * @param {httpConfig} [config] 301 | */ 302 | function httpPutDocs(url, data, config) { 303 | } 304 | /** 305 | * @param {string} url 306 | * @param {*} data 307 | * @param {httpConfig} [config] 308 | */ 309 | function httpPatchDocs(url, data, config) { 310 | } 311 | 312 | intellisense.annotate($delegate.get, httpGetDocs); 313 | intellisense.annotate($delegate.delete, httpDeleteDocs); 314 | intellisense.annotate($delegate.jsonp, httpJsonpDocs); 315 | intellisense.annotate($delegate.head, httpHeadDocs); 316 | intellisense.annotate($delegate.post, httpPostDocs); 317 | intellisense.annotate($delegate.put, httpPutDocs); 318 | intellisense.annotate($delegate.patch, httpPatchDocs); 319 | return $delegate; 320 | }]); 321 | 322 | // Decorate the $q service to resolve deferred objects at the end of the digest cycle. 323 | $provide.decorator('$q', ['$rootScope', '$delegate', function ($rootScope, $delegate) { 324 | var originalDefer = $delegate.defer; 325 | 326 | $delegate.defer = function () { 327 | // Create a deferred object. 328 | var deferred = originalDefer.apply($delegate, arguments); 329 | var promise = deferred.promise; 330 | 331 | // Override the promise methods to call handlers after the digest cycle. 332 | // This allows them to be called with parameters by user code, but if they 333 | // are never called on the first digest cycle they will still get Intellisense 334 | // on closure variables. 335 | function callArgsAfterDigest(originalFunc) { 336 | return function () { 337 | forEach(arguments, function (argument) { 338 | if (angular.isFunction(argument)) { 339 | $rootScope.$$postDigest(argument); 340 | } 341 | }); 342 | 343 | return originalFunc.apply(promise, arguments); 344 | }; 345 | } 346 | 347 | var originalThen = promise.then, 348 | originalCatch = promise['catch'], 349 | originalFinally = promise['finally']; 350 | 351 | promise.then = callArgsAfterDigest(originalThen); 352 | promise['catch'] = callArgsAfterDigest(originalCatch); 353 | promise['finally'] = callArgsAfterDigest(originalFinally); 354 | 355 | return deferred; 356 | }; 357 | 358 | return $delegate; 359 | }]); 360 | 361 | // Decorate the $httpBackend service to execute the callback rather than using 362 | // XHR, so that functions handling the response are called during Intellisense. 363 | $provide.decorator('$httpBackend', [function () { 364 | return function (method, url, post, callback) { 365 | callback(200, undefined, '', 'OK'); 366 | }; 367 | }]); 368 | 369 | // Decorate the $rootScope to always call event listeners registered 370 | // with $on, so that listener functions are called during Intellisense. 371 | $provide.decorator('$rootScope', ['$delegate', function ($delegate) { 372 | var original$On = $delegate.$on; 373 | 374 | $delegate.$on = function (name) { 375 | $delegate.$$postDigest(function () { 376 | $delegate.$emit(name); 377 | }); 378 | 379 | return original$On.apply($delegate, arguments); 380 | }; 381 | 382 | return $delegate; 383 | }]); 384 | }]); 385 | 386 | // Decorate angular.forEach to always call the callback once, even if it wouldn't 387 | // normally be called, so that closure variables will be available via Intellisense. 388 | var originalForEach = angular.forEach; 389 | 390 | angular.forEach = function (obj, iterator, context) { 391 | var iteratorCalled = false; 392 | originalForEach.call(angular, obj, function () { 393 | iteratorCalled = true; 394 | iterator.apply(context, arguments); 395 | }, context); 396 | 397 | if (!iteratorCalled) { 398 | iterator.call(context, undefined, ''); 399 | } 400 | 401 | return obj; 402 | }; 403 | 404 | intellisense.redirectDefinition(angular.forEach, originalForEach); 405 | 406 | function isAngularModule(obj) { 407 | // Angular modules all have names and invoke queues and core provider functions. 408 | return angular.isObject(obj) && 409 | angular.isString(obj.name) && 410 | angular.isArray(obj._invokeQueue) && 411 | angular.isFunction(obj.provider) && 412 | angular.isFunction(obj.constant) && 413 | angular.isFunction(obj.value) && 414 | angular.isFunction(obj.factory) && 415 | angular.isFunction(obj.service); 416 | } 417 | 418 | // Decorate the angular.module function to record the name of each module as it is registered. 419 | var originalModuleFunction = angular.module; 420 | 421 | angular.module = function (name, requires, configFn) { 422 | var hasModuleBeenTracked = requiredModuleMap[name] !== undefined; 423 | 424 | logMessage(LOG_LEVEL.VERBOSE, 'Calling "angular.module" with the following arguments:'); 425 | logValue(LOG_LEVEL.VERBOSE, arguments); 426 | 427 | // If the module has not yet been tracked, then call the original module function with all of the specified arguments. 428 | // Otherwise, call the original module function with only the module name. 429 | // (This prevents the module from being recreated if the module is being declared in this file but is referred to in other files.) 430 | var returnValue = hasModuleBeenTracked ? 431 | originalModuleFunction.call(angular, name) : 432 | originalModuleFunction.apply(angular, arguments); 433 | 434 | // When editing a file that initially creates a module (e.g. via angular.module('name', ['dependency'])) 435 | // it's likely that the JS editor has already executed code that uses that module and did not define dependencies 436 | // (e.g. via angular.module('name')). This line of code makes sure that the dependencies are maintained. 437 | if (!returnValue.requires && requires) { 438 | returnValue.requires = requires; 439 | } 440 | 441 | // HACK: For some reason, the implicit require of the 'ng' module gets dropped in the call to the originalModuleFunction 442 | // this re-adds it if it wasn't explicit 443 | if (angular.isArray(returnValue.requires) && requires.indexOf('ng') == -1) { 444 | returnValue.requires = requires.concat('ng'); 445 | } 446 | 447 | // Ensure that the module and its dependencies are tracked, and all of its provider functions run. 448 | trackModule(returnValue); 449 | 450 | // Call the configuration function if one is specified. 451 | if (configFn) { 452 | returnValue.config(configFn); 453 | } 454 | 455 | return returnValue; 456 | }; 457 | intellisense.redirectDefinition(angular.module, originalModuleFunction); 458 | 459 | function trackModule(moduleOrName) { 460 | var moduleName, module; 461 | 462 | // Tell the JavaScript editor that progress is being made in building the 463 | // IntelliSense simulation, giving us more time to process modules before timing out 464 | intellisense.progress(); 465 | 466 | if (angular.isString(moduleOrName)) { 467 | // If the argument is a module name, retrieve the module from the angular.module function. 468 | moduleName = moduleOrName; 469 | module = originalModuleFunction.call(angular, moduleName); 470 | } else { 471 | // Otherwise the argument is a module, so get the name from its name property. 472 | module = moduleOrName; 473 | moduleName = module.name; 474 | } 475 | 476 | if (requiredModuleMap[moduleName] === undefined) { 477 | logMessage(LOG_LEVEL.INFO, 'Tracking module "' + moduleName + '".'); 478 | 479 | // Store the module name mapped to the names of all required modules. 480 | var requiredModuleNames = [moduleName]; 481 | 482 | // Recursively process dependent modules. 483 | forEach(module.requires, function (requiredModuleName) { 484 | trackModule(requiredModuleName); 485 | requiredModuleNames.splice(requiredModuleNames.length, 0, requiredModuleMap[requiredModuleName]); 486 | }); 487 | 488 | requiredModuleMap[moduleName] = requiredModuleNames; 489 | 490 | // Decorate module provider functions. 491 | decorateModuleProviderFunctions(module); 492 | } 493 | } 494 | 495 | function decorateModuleProviderFunctions(module) { 496 | function addNavBarOverride(name, providerFn, callBackDefinition) { 497 | if (!intellisense.declareNavigationContainer) { 498 | return; 499 | } 500 | 501 | // When the callback defintion is an array, pull the actual callback off the end 502 | if (angular.isArray(callBackDefinition)) { 503 | callBackDefinition = callBackDefinition[callBackDefinition.length - 1]; 504 | } 505 | 506 | // Add an entry to the nav bar for the current provider function 507 | intellisense.declareNavigationContainer( 508 | { callback: callBackDefinition }, 509 | name + ' (' + providerFn + ')', 510 | 'vs:GlyphGroupType') 511 | } 512 | 513 | // Initialize each component with empty object dependencies. 514 | forEach(moduleProviderFunctions, function (providerFunction) { 515 | 516 | // Decorate the component type function to call component functions with correct arguments. 517 | var originalProviderFunction = module[providerFunction]; 518 | 519 | // Only decorate the provider function if the module has it (which it may not for animate). 520 | if (originalProviderFunction) { 521 | module[providerFunction] = function (name, callBackDefinition) { 522 | logMessage(LOG_LEVEL.VERBOSE, 'Calling provider function "' + providerFunction + '" with the following arguments:'); 523 | logValue(LOG_LEVEL.VERBOSE, arguments); 524 | 525 | // Call the original component type function. 526 | var returnValue = originalProviderFunction.apply(module, arguments); 527 | 528 | // Create an injector for the module. 529 | // (This will execute all configuration and run blocks.) 530 | var injector = angular.injector(requiredModuleMap[module.name]); 531 | 532 | if (arguments.length === 2) { 533 | var component; 534 | var locals; 535 | 536 | // Factories, services, providers, etc. 537 | logMessage(LOG_LEVEL.INFO, 'Creating instance of ' + providerFunction + ' "' + arguments[0] + '".'); 538 | addNavBarOverride(name, providerFunction, callBackDefinition); 539 | 540 | // Before calling the injector, make sure the JS editor knows that progress has been made. 541 | // This helps avoid a "timeout" situation. 542 | intellisense.progress(); 543 | 544 | // Initialize the component based on the provider function. 545 | switch (providerFunction) { 546 | case 'factory': 547 | case 'service': 548 | component = injector.get(arguments[0]); 549 | 550 | break; 551 | case 'provider': 552 | var component = arguments[1]; 553 | 554 | if (angular.isArray(component) || angular.isFunction(component)) { 555 | component = providerInjector.instantiate(component); 556 | } 557 | 558 | break; 559 | case 'controller': 560 | // Create locals to aid with injection. 561 | locals = { 562 | '$scope': injector.get('$rootScope').$new() 563 | }; 564 | 565 | component = injector.get('$controller')(arguments[0], locals); 566 | 567 | break; 568 | case 'filter': 569 | component = injector.get('$filter')(arguments[0]); 570 | 571 | break; 572 | case 'directive': 573 | case 'animation': 574 | // Create an instance of the directive/animation definition object. 575 | component = injector.invoke(arguments[1]); 576 | 577 | break; 578 | } 579 | 580 | logMessage(LOG_LEVEL.VERBOSE, 'Creating instance of ' + providerFunction + ' "' + arguments[0] + '" returned the following:'); 581 | logValue(LOG_LEVEL.VERBOSE, component); 582 | 583 | if (providerFunction === 'directive') { 584 | // HACK: (JMB) Execute directive functions with AngularJS mocks. 585 | var controller, 586 | $scope = injector.get('$rootScope').$new(), 587 | element = angular.element(), 588 | attrs = new Attributes(), 589 | transclude = function (scope, cloneLinkFn) { }; 590 | 591 | if (component.controller) { 592 | logMessage(LOG_LEVEL.INFO, 'Calling function "controller" on directive definition object.'); 593 | 594 | controller = injector.instantiate(component.controller, { 595 | '$scope': $scope, 596 | '$element': element, 597 | '$attrs': attrs, 598 | '$transclude': transclude 599 | }); 600 | 601 | logMessage(LOG_LEVEL.VERBOSE, 'Calling function "controller" on directive definition object returned the following:'); 602 | logValue(LOG_LEVEL.VERBOSE, controller); 603 | } 604 | 605 | if (component.compile) { 606 | logMessage(LOG_LEVEL.INFO, 'Calling function "compile" on directive definition object.'); 607 | 608 | // Set the result of the compile function as the directive link function, so it is called as well. 609 | component.link = component.compile(element, attrs, transclude); 610 | 611 | logMessage(LOG_LEVEL.VERBOSE, 'Calling function "compile" on directive definition object returned the following:'); 612 | logValue(LOG_LEVEL.VERBOSE, component.link); 613 | } 614 | 615 | if (component.link) { 616 | if (angular.isFunction(component.link)) { 617 | logMessage(LOG_LEVEL.INFO, 'Calling function "link" on directive definition object.'); 618 | 619 | var returnValue = component.link($scope, element, attrs, controller, transclude); 620 | 621 | logMessage(LOG_LEVEL.VERBOSE, 'Calling function "link" on directive definition object returned the following:'); 622 | logValue(LOG_LEVEL.VERBOSE, returnValue); 623 | } 624 | if (component.link.pre) { 625 | logMessage(LOG_LEVEL.INFO, 'Calling function "link.pre" on directive definition object.'); 626 | 627 | var returnValue = component.link.pre($scope, element, attrs, controller, transclude); 628 | 629 | logMessage(LOG_LEVEL.VERBOSE, 'Calling function "link.pre" on directive definition object returned the following:'); 630 | logValue(LOG_LEVEL.VERBOSE, returnValue); 631 | } 632 | if (component.link.post) { 633 | logMessage(LOG_LEVEL.INFO, 'Calling function "link.post" on directive definition object.'); 634 | 635 | var returnValue = component.link.post($scope, element, attrs, controller, transclude); 636 | 637 | logMessage(LOG_LEVEL.VERBOSE, 'Calling function "link.post" on directive definition object returned the following:'); 638 | logValue(LOG_LEVEL.VERBOSE, returnValue); 639 | } 640 | } 641 | } else if (providerFunction === 'animation') { 642 | // HACK: (JMB) Execute animate functions with AngularJS mocks. 643 | var element = angular.element(), 644 | doneCallback = function () { }; 645 | 646 | forEach(component, function (value, key) { 647 | if (angular.isFunction(value)) { 648 | var returnValue; 649 | logMessage(LOG_LEVEL.INFO, 'Calling function "' + key + '" on animation definition object.'); 650 | 651 | switch (key) { 652 | case 'enter': 653 | case 'leave': 654 | case 'move': 655 | returnValue = value.call(component, element, doneCallback); 656 | break; 657 | case 'addClass': 658 | case 'removeClass': 659 | returnValue = value.call(component, element, '', doneCallback); 660 | break; 661 | default: 662 | returnValue = value.call(component); 663 | break; 664 | } 665 | 666 | logMessage(LOG_LEVEL.VERBOSE, 'Calling function "' + key + '" on animation definition object returned the following:'); 667 | logValue(LOG_LEVEL.VERBOSE, returnValue); 668 | } 669 | }); 670 | } else { 671 | // Execute all functions on the initialized component. 672 | callComponentFunctions(injector, component, locals); 673 | } 674 | 675 | // Digest the root scope to force promise resolution. 676 | injector.get('$rootScope').$digest(); 677 | 678 | return returnValue; 679 | } else { 680 | // In all other cases, force the provider function to behave the same as normal 681 | // This was required for .config() functions to work correctly. 682 | return returnValue; 683 | } 684 | }; 685 | } 686 | }); 687 | } 688 | function callComponentFunctions(injector, component, locals, recursionDepth) { 689 | // A recursion guard, to prevent this code from recursing too long and 690 | // causing the IntelliSense engine to timeout 691 | if (!recursionDepth) { 692 | recursionDepth = 0; 693 | } 694 | if (recursionDepth++ >= 2) { 695 | return; 696 | } 697 | 698 | // Tell the JavaScript editor that progress is being made in building the 699 | // IntelliSense simulation, giving us more time to call component functions before timing out 700 | intellisense.progress(); 701 | 702 | if (component) { 703 | if (angular.isElement(component) || angular.isString(component) || angular.isNumber(component) || angular.isDate(component)) { 704 | // Bypass calling component functions when there likely aren't any user-defined 705 | // functions to call 706 | return; 707 | } else if (angular.isArray(component) || angular.isFunction(component)) { 708 | // If the component itself is a function, then call it. 709 | logMessage(LOG_LEVEL.INFO, 'Calling component as function.'); 710 | var returnValue = injector.invoke(component, null, locals); 711 | logMessage(LOG_LEVEL.VERBOSE, 'Calling component as function returned the following:'); 712 | logValue(LOG_LEVEL.VERBOSE, returnValue); 713 | 714 | // Recursively call functions on the return value. 715 | callComponentFunctions(injector, returnValue, locals, recursionDepth); 716 | } else { 717 | logMessage(LOG_LEVEL.VERBOSE, 'Calling all functions on the following component:'); 718 | logValue(LOG_LEVEL.VERBOSE, component); 719 | 720 | // Call each function that is a property of the component. 721 | forEach(component, function (value, key) { 722 | if (angular.isArray(value) || angular.isFunction(value)) { 723 | logMessage(LOG_LEVEL.INFO, 'Calling function "' + key + '" on component.'); 724 | var returnValue = injector.invoke(value, component, locals); 725 | logMessage(LOG_LEVEL.VERBOSE, 'Calling function "' + key + '" on component returned the following:'); 726 | logValue(LOG_LEVEL.VERBOSE, returnValue); 727 | 728 | // Recursively call functions on the return value. 729 | callComponentFunctions(injector, returnValue, locals, recursionDepth); 730 | } 731 | }); 732 | } 733 | } 734 | } 735 | 736 | // Always add the AngularJS core module. 737 | trackModule('ng'); 738 | 739 | //#endregion 740 | 741 | //#region Jasmine Intellisense 742 | 743 | if (jasmine) { 744 | // Create an array of functions to override. 745 | var overrides = [ 746 | 'describe', 'xdescribe', 747 | 'beforeEach', 'afterEach', 748 | 'it', 'xit', 749 | 'expect', 750 | 'module', 'inject', 751 | {source: angular.mock, method: 'module'}, 752 | {source: angular.mock, method: 'inject'} 753 | ]; 754 | 755 | var jasmineInjector; 756 | 757 | forEach(overrides, function (override) { 758 | // Extract source and method from the override. 759 | var source = override.source || window; 760 | var method = override.method || override; 761 | 762 | source[method] = function () { 763 | // Don't actually call the original method, since it interferes with Intellisense. 764 | 765 | if (method == 'module') { 766 | // Track each named module, call each anonymous module. 767 | forEach(arguments, function (argument) { 768 | if (angular.isString(argument)) { 769 | // Track the module. 770 | trackModule(argument); 771 | 772 | // (Re)create an injector for the module. 773 | jasmineInjector = angular.injector(requiredModuleMap[argument]); 774 | } else if (angular.isFunction(argument) || angular.isArray(argument)) { 775 | // Invoke the module configuration function. 776 | providerInjector.invoke(argument); 777 | } 778 | }); 779 | } else if (method == 'inject') { 780 | // Perform injection on each argument of the method. 781 | forEach(arguments, function (argument) { 782 | jasmineInjector.invoke(argument); 783 | }); 784 | } else { 785 | // Otherwise, call any function arguments to the method. 786 | forEach(arguments.filter(angular.isFunction), function (argument) { 787 | argument(); 788 | }); 789 | 790 | if (method === 'expect') { 791 | // Return an expectation from calls to expect. 792 | return jasmine.Expectation.Factory(); 793 | } 794 | } 795 | }; 796 | }); 797 | } 798 | 799 | //#endregion 800 | 801 | // Filter out private AngularJS properties (prefixed with $$) from statement completion. 802 | if (intellisense && intellisense.addEventListener) { 803 | intellisense.addEventListener('statementcompletion', function (event) { 804 | var filterRegex = /^\$\$.*/; 805 | 806 | event.items = event.items.filter(function (item) { 807 | return !filterRegex.test(item.name); 808 | }); 809 | }); 810 | } 811 | })(window.intellisense); -------------------------------------------------------------------------------- /src/Scripts/tests/basic-tests.js: -------------------------------------------------------------------------------- 1 | _$AngularJS_VisualStudio_Intellisense.setLogLevelVerbose(); 2 | (function (angular) { 3 | // Create a test module. 4 | var testApp = angular.module('tests', ['ng', 'ngAnimate', 'ngRoute'], ['$logProvider', function (logProvider) { 5 | // TEST: Providers can be injected into module config functions in module declaration. 6 | //logProvider. 7 | }]).config(['$locationProvider', function ($locationProvider) { 8 | $locationProvider.html5Mode = true; 9 | }]).factory('chainedFactory', function ($location) { 10 | // TEST: Components can be injected into chained provider functions. 11 | // TEST: Components can be injected by function parameter name. 12 | //$location. 13 | }); 14 | 15 | testApp.constant('testConstant', { foo: 1, bar: 2 }); 16 | 17 | // Create a test provider. 18 | testApp.provider('testComponent', ['$logProvider', 'testConstant', function (logProvider, testConstant) { 19 | // TEST: Constants can be injected into other provider functions 20 | //testConstant. 21 | // TEST: Providers can be injected into other provider functions. 22 | //logProvider. 23 | 24 | this.someProviderField = 42; 25 | this.someProviderFunction = function () { 26 | // TEST: Providers injected into provider functions can be referenced in closures. 27 | //logProvider. 28 | }; 29 | 30 | this.$get = ['$q', function ($q) { 31 | // TEST: Components can be injected into provider $get functions. 32 | //$q. 33 | 34 | return { 35 | someField: 21, 36 | someFunction: function ( $scope ) { 37 | // TEST: Components injected into provider $get functions can be referenced in closures. 38 | //$q. 39 | } 40 | }; 41 | }]; 42 | }]); 43 | 44 | // Create another test provider 45 | testApp.provider('testAnotherComponent', ['testComponentProvider', function (componentProvider) { 46 | // TEST: Providers can be injected into other providers. 47 | //componentProvider. 48 | }]); 49 | 50 | // Create a test factory. 51 | testApp.factory('testFactory', ['$q', 'testComponent', function ($q, component) { 52 | // TEST: Components can be injected into factory functions. 53 | //$q. 54 | //component. 55 | return { 56 | someFactoryField: 21, 57 | someFactoryFunction: function () { 58 | // TEST: Components injected into factory functions can be referenced in closures. 59 | //$q. 60 | //component. 61 | 62 | var foo = function (param) { 63 | // TEST: Components injected into factory functions can be referenced in multiple levels of closures. 64 | //$q. 65 | //component. 66 | //param. 67 | } 68 | 69 | return function () { 70 | foo(component); 71 | 72 | // TEST: Components injected into factory functions can be referenced in multiple levels of closures. 73 | //$q. 74 | //component. 75 | 76 | 77 | }; 78 | } 79 | }; 80 | }]); 81 | 82 | // Create a test service. 83 | testApp.service('testService', ['$q', 'testFactory', function ($q, factory) { 84 | // TEST: Components can be injected into factory functions. 85 | //$q. 86 | //factory. 87 | return { foo: true }; 88 | }]); 89 | 90 | testApp.controller('testController', function ($scope) { 91 | // TEST: $scope can be injected into controller functions. 92 | //$scope. 93 | }); 94 | 95 | // Create a test config block. 96 | testApp.config(['testComponentProvider', function (componentProvider) { 97 | // TEST: Providers can be injected into config blocks. 98 | //componentProvider. 99 | }]); 100 | 101 | // Create a test run block. 102 | testApp.run(['testFactory', function (component) { 103 | // TEST: Components can be injected into run blocks. 104 | //component. 105 | }]); 106 | 107 | // Create a test animate block. 108 | testApp.animation('testAnimation', function (testFactory) { 109 | // TEST: Components can be injected into animation factory functions. 110 | //testFactory. 111 | 112 | return { 113 | enter: function (element, callback) { 114 | // TEST: Components can be injected into animation functions. 115 | // testFactory. 116 | } 117 | }; 118 | }); 119 | 120 | testApp.service("objectLiteralInteliSenseTestService", function ($q, $http) { 121 | // TEST: 122 | //$http. 123 | //$q. 124 | //$http.get('http://test', { 125 | return {foo: true}; 126 | }) 127 | })(angular); 128 | 129 | (function (angular) { 130 | // Tests modules not bound to variables/global 131 | angular.module("isolatedTestApp", ['ngRoute', 'tests']) 132 | 133 | angular.module('isolatedTestApp').config(['$routeProvider', function (routeProvider) { 134 | // TEST: Providers can be injected into config blocks, in modules that are not 135 | // bound to a variable or globally exposed 136 | //routeProvider. 137 | }]); 138 | })(angular); 139 | 140 | (function (angular) { 141 | angular.module('sameServiceModule1', []) 142 | .service("service", function () { 143 | return { valueFromModule1: true }; 144 | }); 145 | 146 | angular.module('sameServiceModule2', []) 147 | .service("service", function () { 148 | return { valueFromModule2: true }; 149 | }); 150 | 151 | angular.module("sameServiceIn2ModsTest", ['sameServiceModule1']) 152 | .controller('MyController', function (service) { 153 | // TEST: the service from "module1" should be shown, not the service of the same name from "module2" 154 | //service. 155 | }); 156 | })(angular); -------------------------------------------------------------------------------- /src/Scripts/tests/multi-file-test/multi-file-test1.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('multi-file', ['ng']) 5 | 6 | .service('service1', function ($http) { 7 | // TEST: a completion list shows up below 8 | // $http. 9 | return { member1: 'test' }; 10 | }); 11 | })(); -------------------------------------------------------------------------------- /src/Scripts/tests/multi-file-test/multi-file-test2.js: -------------------------------------------------------------------------------- 1 | /// 2 | (function () { 3 | 'use strict'; 4 | 5 | angular.module('multi-file') 6 | 7 | .controller('myController', function (service1) { 8 | // TEST: a completion list shows up below 9 | //service1. 10 | }); 11 | })(); -------------------------------------------------------------------------------- /src/Scripts/tests/test-ngModuleIncludedByDefault.js: -------------------------------------------------------------------------------- 1 | /// 2 | // NOTE: Do NOT include this file in _references.js. It's meant to test IntelliSense in the absence of any 3 | // other Angular code executing. 4 | ( function ( angular ) { 5 | angular.module( "implicitNgModTest", [] ) 6 | .controller( 'MyController', function ( $location ) { 7 | // TEST: the 'ng' module should be implicitly included by default and core services "just work" 8 | //$location. 9 | } ); 10 | } )( angular ); -------------------------------------------------------------------------------- /src/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | --------------------------------------------------------------------------------