From 5625dd9b21c81b4962ab5c0c3358617bc98137c6 Mon Sep 17 00:00:00 2001 From: Solomon English Date: Wed, 13 Nov 2013 11:30:06 -0800 Subject: [PATCH] Initial commit --- .gitignore | 5 + Gruntfile.coffee | 226 + Procfile | 1 + client/images/lauren-cats.jpg | Bin 0 -> 204817 bytes client/js/app.coffee | 9 + client/js/controllers/page1.coffee | 18 + client/js/controllers/page2.coffee | 11 + client/js/controllers/root.coffee | 15 + client/js/directives/app-version.coffee | 12 + client/js/filters/interpolate.coffee | 11 + client/js/filters/static.coffee | 12 + client/js/main.coffee | 46 + client/js/require-config.coffee | 50 + client/js/routes.coffee | 15 + client/js/services/remote.coffee | 13 + client/js/services/version.coffee | 5 + client/less/app.less | 6 + client/less/bootstrap/alerts.less | 67 + client/less/bootstrap/badges.less | 51 + client/less/bootstrap/bootstrap.less | 49 + client/less/bootstrap/breadcrumbs.less | 23 + client/less/bootstrap/button-groups.less | 253 + client/less/bootstrap/buttons.less | 158 + client/less/bootstrap/carousel.less | 231 + client/less/bootstrap/close.less | 33 + client/less/bootstrap/code.less | 53 + .../less/bootstrap/component-animations.less | 29 + client/less/bootstrap/dropdowns.less | 192 + client/less/bootstrap/forms.less | 364 + client/less/bootstrap/glyphicons.less | 237 + client/less/bootstrap/grid.less | 93 + client/less/bootstrap/input-groups.less | 136 + client/less/bootstrap/jumbotron.less | 40 + client/less/bootstrap/labels.less | 58 + client/less/bootstrap/list-group.less | 88 + client/less/bootstrap/media.less | 56 + client/less/bootstrap/mixins.less | 858 + client/less/bootstrap/modals.less | 132 + client/less/bootstrap/navbar.less | 624 + client/less/bootstrap/navs.less | 262 + client/less/bootstrap/normalize.less | 406 + client/less/bootstrap/pager.less | 55 + client/less/bootstrap/pagination.less | 85 + client/less/bootstrap/panels.less | 172 + client/less/bootstrap/popovers.less | 133 + client/less/bootstrap/print.less | 105 + client/less/bootstrap/progress-bars.less | 92 + .../less/bootstrap/responsive-utilities.less | 209 + client/less/bootstrap/scaffolding.less | 119 + client/less/bootstrap/tables.less | 236 + client/less/bootstrap/theme.less | 247 + client/less/bootstrap/thumbnails.less | 30 + client/less/bootstrap/tooltip.less | 95 + client/less/bootstrap/type.less | 279 + client/less/bootstrap/utilities.less | 56 + client/less/bootstrap/variables.less | 637 + client/less/bootstrap/wells.less | 29 + client/less/pages/all.less | 8 + client/less/pages/first-page.less | 3 + client/lib/almond.js | 410 + client/lib/angular/angular-animate.js | 1226 + client/lib/angular/angular-cookies.js | 202 + client/lib/angular/angular-loader.js | 323 + client/lib/angular/angular-resource.js | 578 + client/lib/angular/angular-route.js | 880 + client/lib/angular/angular-sanitize.js | 577 + client/lib/angular/angular-touch.js | 563 + client/lib/angular/angular.js | 20031 ++++++++++++++++ client/lib/bootstrap/affix.js | 126 + client/lib/bootstrap/alert.js | 98 + client/lib/bootstrap/button.js | 109 + client/lib/bootstrap/carousel.js | 217 + client/lib/bootstrap/collapse.js | 179 + client/lib/bootstrap/dropdown.js | 154 + client/lib/bootstrap/modal.js | 246 + client/lib/bootstrap/popover.js | 117 + client/lib/bootstrap/scrollspy.js | 158 + client/lib/bootstrap/tab.js | 135 + client/lib/bootstrap/tooltip.js | 386 + client/lib/bootstrap/transition.js | 56 + client/lib/jquery-1.10.2.js | 9789 ++++++++ client/lib/require.js | 2054 ++ client/lib/underscore.js | 1276 + client/partials/pages/first-page.html | 12 + client/partials/pages/second-page.html | 9 + package.json | 43 + s-seed.sublime-project | 23 + server/app.coffee | 33 + server/routes/data.coffee | 2 + server/routes/index.coffee | 8 + server/routes/pages.coffee | 8 + server/views/index.ejs | 56 + 92 files changed, 47622 insertions(+) create mode 100644 .gitignore create mode 100644 Gruntfile.coffee create mode 100644 Procfile create mode 100644 client/images/lauren-cats.jpg create mode 100644 client/js/app.coffee create mode 100644 client/js/controllers/page1.coffee create mode 100644 client/js/controllers/page2.coffee create mode 100644 client/js/controllers/root.coffee create mode 100644 client/js/directives/app-version.coffee create mode 100644 client/js/filters/interpolate.coffee create mode 100644 client/js/filters/static.coffee create mode 100644 client/js/main.coffee create mode 100644 client/js/require-config.coffee create mode 100644 client/js/routes.coffee create mode 100644 client/js/services/remote.coffee create mode 100644 client/js/services/version.coffee create mode 100644 client/less/app.less create mode 100755 client/less/bootstrap/alerts.less create mode 100755 client/less/bootstrap/badges.less create mode 100755 client/less/bootstrap/bootstrap.less create mode 100755 client/less/bootstrap/breadcrumbs.less create mode 100755 client/less/bootstrap/button-groups.less create mode 100755 client/less/bootstrap/buttons.less create mode 100755 client/less/bootstrap/carousel.less create mode 100755 client/less/bootstrap/close.less create mode 100755 client/less/bootstrap/code.less create mode 100755 client/less/bootstrap/component-animations.less create mode 100755 client/less/bootstrap/dropdowns.less create mode 100755 client/less/bootstrap/forms.less create mode 100755 client/less/bootstrap/glyphicons.less create mode 100755 client/less/bootstrap/grid.less create mode 100755 client/less/bootstrap/input-groups.less create mode 100755 client/less/bootstrap/jumbotron.less create mode 100755 client/less/bootstrap/labels.less create mode 100755 client/less/bootstrap/list-group.less create mode 100755 client/less/bootstrap/media.less create mode 100755 client/less/bootstrap/mixins.less create mode 100755 client/less/bootstrap/modals.less create mode 100755 client/less/bootstrap/navbar.less create mode 100755 client/less/bootstrap/navs.less create mode 100755 client/less/bootstrap/normalize.less create mode 100755 client/less/bootstrap/pager.less create mode 100755 client/less/bootstrap/pagination.less create mode 100755 client/less/bootstrap/panels.less create mode 100755 client/less/bootstrap/popovers.less create mode 100755 client/less/bootstrap/print.less create mode 100755 client/less/bootstrap/progress-bars.less create mode 100755 client/less/bootstrap/responsive-utilities.less create mode 100755 client/less/bootstrap/scaffolding.less create mode 100755 client/less/bootstrap/tables.less create mode 100755 client/less/bootstrap/theme.less create mode 100755 client/less/bootstrap/thumbnails.less create mode 100755 client/less/bootstrap/tooltip.less create mode 100755 client/less/bootstrap/type.less create mode 100755 client/less/bootstrap/utilities.less create mode 100755 client/less/bootstrap/variables.less create mode 100755 client/less/bootstrap/wells.less create mode 100644 client/less/pages/all.less create mode 100644 client/less/pages/first-page.less create mode 100644 client/lib/almond.js create mode 100755 client/lib/angular/angular-animate.js create mode 100755 client/lib/angular/angular-cookies.js create mode 100755 client/lib/angular/angular-loader.js create mode 100755 client/lib/angular/angular-resource.js create mode 100755 client/lib/angular/angular-route.js create mode 100755 client/lib/angular/angular-sanitize.js create mode 100755 client/lib/angular/angular-touch.js create mode 100755 client/lib/angular/angular.js create mode 100755 client/lib/bootstrap/affix.js create mode 100755 client/lib/bootstrap/alert.js create mode 100755 client/lib/bootstrap/button.js create mode 100755 client/lib/bootstrap/carousel.js create mode 100755 client/lib/bootstrap/collapse.js create mode 100755 client/lib/bootstrap/dropdown.js create mode 100755 client/lib/bootstrap/modal.js create mode 100755 client/lib/bootstrap/popover.js create mode 100755 client/lib/bootstrap/scrollspy.js create mode 100755 client/lib/bootstrap/tab.js create mode 100755 client/lib/bootstrap/tooltip.js create mode 100755 client/lib/bootstrap/transition.js create mode 100644 client/lib/jquery-1.10.2.js create mode 100644 client/lib/require.js create mode 100644 client/lib/underscore.js create mode 100644 client/partials/pages/first-page.html create mode 100644 client/partials/pages/second-page.html create mode 100644 package.json create mode 100644 s-seed.sublime-project create mode 100644 server/app.coffee create mode 100644 server/routes/data.coffee create mode 100644 server/routes/index.coffee create mode 100644 server/routes/pages.coffee create mode 100644 server/views/index.ejs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8365c0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +public/css +public/js +s-seed.sublime-workspace +build \ No newline at end of file diff --git a/Gruntfile.coffee b/Gruntfile.coffee new file mode 100644 index 0000000..5828526 --- /dev/null +++ b/Gruntfile.coffee @@ -0,0 +1,226 @@ +_ = require "underscore" +Path = require "path" + +module.exports = (grunt) -> + grunt.loadNpmTasks "grunt-contrib-coffee" + grunt.loadNpmTasks "grunt-contrib-less" + grunt.loadNpmTasks "grunt-contrib-requirejs" + grunt.loadNpmTasks "grunt-contrib-watch" + grunt.loadNpmTasks "grunt-contrib-cssmin" + grunt.loadNpmTasks "grunt-contrib-copy" + grunt.loadNpmTasks "grunt-contrib-clean" + + grunt.loadNpmTasks "grunt-newer" + grunt.loadNpmTasks "grunt-nodemon" + grunt.loadNpmTasks "grunt-concurrent" + grunt.loadNpmTasks "grunt-angular-templates" + grunt.loadNpmTasks "grunt-filerev" + grunt.loadNpmTasks "grunt-filerev-assets" + + + grunt.registerTask "default", ["build"] + + # combine the javascript files using the requirejs config + grunt.registerTask "jscombine", "Require.Js optimization", -> + config = require "./client/js/require-config.coffee" + + rjsClientConfig = config.requireConfig + + # client build config needs some extra items + _.extend rjsClientConfig, + name: './lib/almond' + baseUrl: "build/temp" + include: ["js/main.js"] + out: "./build/public/js/combined.js" + + grunt.config.set "requirejs", + optimize: + options: rjsClientConfig + + grunt.task.run "requirejs" + + grunt.registerTask "server", ["builddev", "concurrent"] + + grunt.registerTask "builddev", [ + # removing the public dir + "clean:public" + + # turn the client coffee into js into the public folder + "coffee:dev" + + # compile the less CSS + "less:dev" + + # create the single template file + "ngtemplates" + ] + + grunt.registerTask "build", [ + # remove build folder + "clean:build" + + # turn the client coffee into js in the build/temp + "coffee:prod" + + # compile the templates + "ngtemplates" + + # copy the templates and lib to build/temp + "copy:client" + + # combine all the js files + "jscombine" + + # compile the css + "less:prod" + + # minify the css + "cssmin" + + # set versions on all the files + "filerev" + + # create a server map + "filerev_assets" + + # copy over server coffee files + "copy:server" + + # copy over deploy files + "copy:heroku" + + # remove the temp files + "clean:temp" + ] + + grunt.initConfig + + clean: + build: + files: [{ + dot: true + src: [ + "build/*" + "!build/.git*" + ] + }] + + temp: ["build/temp"] + + public: ["public"] + + concurrent: + dev: + tasks: ["nodemon", "watch"] + options: + logConcurrentOutput: true + + nodemon: + dev: + options: + watchedFolders: ["server"] + file: "./server/app.coffee" + + coffee: + dev: + options: + bare: true + sourceMap: true + sourceRoot: "" + + expand: true + cwd: "client" + src: ["./**/*.coffee"] + dest: "public" + ext: ".js" + + prod: + options: + bare: true + sourceMap: false + sourceRoot: "" + + expand: true + cwd: "client" + src: ["./**/*.coffee"] + dest: "build/temp" + ext: ".js" + + less: + dev: + options: yuicompress: true + files: + "./public/css/app.css": "./client/less/app.less" + + prod: + files: + "./build/temp/css/app.css": "./client/less/app.less" + + # put hash numbers on the assets + filerev: + files: + src: [ + "./build/public/js/**/*.js" + "./build/public/lib/**/*.js" + "./build/public/css/**/*.css" + "./build/public/images/**/*.{png,jpg,jpeg,gif,webp,svg}" + ] + + # create a map file for all the assets + filerev_assets: + options: + cwd: "build/public" + dest: "build/server/assets.json" + + cssmin: + minify: + expand: true + cwd: "build/temp" + src: ["css/app.css"] + dest: "build/public" + ext: ".css" + + copy: + client: + files: [ + # external lib files + {expand: true, src: "lib/**/*.js", cwd: "client/", dest: "./build/temp/"} + + # combined template file + {src: "public/js/templates.js", dest: "build/temp/js/templates.js"} + + # image files + {expand: true, src: "images/**", cwd: "client/", dest: "./build/public/"} + ] + server: + files: [ + {src: ["server/**"], dest: "build/"} + ] + heroku: + files: [ + {src: ["Procfile"], dest: "build/"} + {src: "package.json", dest: "build/package.json"} + ] + + ngtemplates: + myApp: + cwd: "./client" + src: "./partials/**/*.html" + dest:"./public/js/templates.js" + options: + htmlmin: collapseWhitespace: true, collapseBooleanAttributes: true + bootstrap: (module, script) -> + "define(['appModule'], function(appModule) {appModule.run(['$templateCache', function($templateCache){ #{script} }])});" + + watch: + coffee: + files: ["**/*.coffee"] + tasks: ["newer:coffee:dev"] + + less: + files: ["./client/less/**/*.less"] + tasks: ["less:dev"] + + ngtemplates: + files: ["client/partials/**.html"] + tasks: ["ngtemplates"] \ No newline at end of file diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..bff4d59 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: coffee server/app.coffee \ No newline at end of file diff --git a/client/images/lauren-cats.jpg b/client/images/lauren-cats.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b2bbd8212af3489c711e72e913bf468a1bfe10b7 GIT binary patch literal 204817 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%0AS(vr)>izC> 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 + + # setup the app + angular.module("myApp", [ + "ngRoute" + ]). + config ["$routeProvider", ($routeProvider) -> + # add app config here + ] \ No newline at end of file diff --git a/client/js/controllers/page1.coffee b/client/js/controllers/page1.coffee new file mode 100644 index 0000000..f6aa657 --- /dev/null +++ b/client/js/controllers/page1.coffee @@ -0,0 +1,18 @@ +define ["appModule"], (appModule) -> + Page1Controller = ($scope, remote) -> + _init = -> + $scope.page = "page1" + + # gets data from a remote server + remote.getData() + .success (data) -> + $scope.myData = data.message + + _init() + + # register the controller + appModule.controller "Page1Controller", [ + "$scope" + "remote" + Page1Controller + ] \ No newline at end of file diff --git a/client/js/controllers/page2.coffee b/client/js/controllers/page2.coffee new file mode 100644 index 0000000..26d4147 --- /dev/null +++ b/client/js/controllers/page2.coffee @@ -0,0 +1,11 @@ +define ["appModule"], (appModule) -> + + Page2Controller = ($scope) -> + $scope.page = "page2" + + + # register the controller + appModule.controller "Page2Controller", [ + "$scope" + Page2Controller + ] \ No newline at end of file diff --git a/client/js/controllers/root.coffee b/client/js/controllers/root.coffee new file mode 100644 index 0000000..5858d15 --- /dev/null +++ b/client/js/controllers/root.coffee @@ -0,0 +1,15 @@ +define ["appModule", "templates"], (appModule) -> + + RootController = ($scope, $route, $location) -> + _init = -> + $scope.location = $location + + _init() + + # register the controller + appModule.controller "rootController", [ + "$scope" + "$route" + "$location" + RootController + ] \ No newline at end of file diff --git a/client/js/directives/app-version.coffee b/client/js/directives/app-version.coffee new file mode 100644 index 0000000..2cd41a1 --- /dev/null +++ b/client/js/directives/app-version.coffee @@ -0,0 +1,12 @@ +define ["appModule"], (appModule) -> + + # Directive + appVersion = (version) -> + (scope, elm, attrs) -> + elm.text version + + # register the directive + appModule.directive "appVersion", [ + "version" + appVersion + ] \ No newline at end of file diff --git a/client/js/filters/interpolate.coffee b/client/js/filters/interpolate.coffee new file mode 100644 index 0000000..4c6cfb5 --- /dev/null +++ b/client/js/filters/interpolate.coffee @@ -0,0 +1,11 @@ +define ["appModule"], (appModule) -> + + # Filter + interpolate = (version) -> + (text) -> + String(text).replace /\%VERSION\%/mg, version + + appModule.filter "interpolate", [ + "version" + interpolate + ] \ No newline at end of file diff --git a/client/js/filters/static.coffee b/client/js/filters/static.coffee new file mode 100644 index 0000000..0c514d6 --- /dev/null +++ b/client/js/filters/static.coffee @@ -0,0 +1,12 @@ +define ["appModule"], (appModule) -> + + ### + Filter to map static content to versioned static content + ### + staticFilter = -> + (text) -> + staticMapping[text] ? text + + appModule.filter "static", [ + staticFilter + ] \ No newline at end of file diff --git a/client/js/main.coffee b/client/js/main.coffee new file mode 100644 index 0000000..b3600a2 --- /dev/null +++ b/client/js/main.coffee @@ -0,0 +1,46 @@ +requirejs.config requireConfig if requireConfig? + +require [ + "angular" + "jquery" + "underscore" + "appModule" + "templates" + + # bootstrap + "lib/bootstrap/affix.js" + "lib/bootstrap/alert.js" + "lib/bootstrap/button.js" + "lib/bootstrap/carousel.js" + "lib/bootstrap/collapse.js" + "lib/bootstrap/dropdown.js" + "lib/bootstrap/modal.js" + "lib/bootstrap/tooltip.js" + "lib/bootstrap/popover.js" + "lib/bootstrap/scrollspy.js" + "lib/bootstrap/tab.js" + "lib/bootstrap/transition.js" + + "lib/angular/angular-route.js" + + # the routes + "js/routes.js" + + # services + "js/services/version.js" + "js/services/remote.js" + + # controllers + "js/controllers/root.js" + "js/controllers/page1.js" + "js/controllers/page2.js" + + # filters + "js/filters/interpolate.js" + "js/filters/static.js" + + # directives + "js/directives/app-version.js" + +], (angular) -> + angular.bootstrap $("html"), ["myApp"] \ No newline at end of file diff --git a/client/js/require-config.coffee b/client/js/require-config.coffee new file mode 100644 index 0000000..e646343 --- /dev/null +++ b/client/js/require-config.coffee @@ -0,0 +1,50 @@ +exp = window ? exports + +exp.requireConfig = + + baseUrl: "/" + + # if we use the order plugin, we only need a shim for underscore + paths: + jquery: "lib/jquery-1.10.2" + underscore: "lib/underscore" + angular: "lib/angular/angular" + + # angular services + appModule: "js/app" + templates: "js/templates" + + shim: + jquery: + exports: "$" + + underscore: + exports: "_" + init: -> _ + + angular: + exports: "angular" + deps: ["jquery"] + init: -> angular + + # for bootstrap + "lib/bootstrap/affix.js": ["jquery"] + "lib/bootstrap/alert.js": ["jquery"] + "lib/bootstrap/button.js": ["jquery"] + "lib/bootstrap/carousel.js": ["jquery"] + "lib/bootstrap/collapse.js": ["jquery"] + "lib/bootstrap/dropdown.js": ["jquery"] + "lib/bootstrap/modal.js": ["jquery"] + "lib/bootstrap/scrollspy.js": ["jquery"] + "lib/bootstrap/tab.js": ["jquery"] + "lib/bootstrap/tooltip.js": ["jquery"] + "lib/bootstrap/popover.js": ["lib/bootstrap/tooltip.js"] + "lib/bootstrap/transition.js": ["jquery"] + "lib/bootstrap/typeahead.js": ["jquery"] + + # angular + "lib/angular/angular-route.js": ["angular"] + "appModule": ["angular"] + "templates": ["appModule"] + +exp.requireConfig \ No newline at end of file diff --git a/client/js/routes.coffee b/client/js/routes.coffee new file mode 100644 index 0000000..a31b918 --- /dev/null +++ b/client/js/routes.coffee @@ -0,0 +1,15 @@ +define ["appModule"], (appModule) -> + + # setup the routes + appModule.config ["$routeProvider", ($routeProvider) -> + + $routeProvider.when "/view1", + templateUrl: "partials/pages/first-page.html" + controller: "Page1Controller" + + $routeProvider.when "/view2", + templateUrl: "partials/pages/second-page.html" + controller: "Page2Controller" + + $routeProvider.otherwise redirectTo: "/view1" + ] \ No newline at end of file diff --git a/client/js/services/remote.coffee b/client/js/services/remote.coffee new file mode 100644 index 0000000..041b425 --- /dev/null +++ b/client/js/services/remote.coffee @@ -0,0 +1,13 @@ +define ["appModule"], (appModule) -> + + class remote + constructor: (@$http) -> + + getData: -> + @$http method: "GET", url: "/getdata" + + # add the service + appModule.factory "remote", [ + "$http" + ($http) -> new remote $http + ] \ No newline at end of file diff --git a/client/js/services/version.coffee b/client/js/services/version.coffee new file mode 100644 index 0000000..4aad9bd --- /dev/null +++ b/client/js/services/version.coffee @@ -0,0 +1,5 @@ +define ["appModule"], (appModule) -> + + # Demonstrate how to register services + # In this case it is a simple value service. + appModule.value 'version', '1.0' \ No newline at end of file diff --git a/client/less/app.less b/client/less/app.less new file mode 100644 index 0000000..857ee4c --- /dev/null +++ b/client/less/app.less @@ -0,0 +1,6 @@ +// Bootstrap +@import "bootstrap/bootstrap.less"; + +// Pages +@import "pages/all.less"; +@import "pages/first-page.less"; \ No newline at end of file diff --git a/client/less/bootstrap/alerts.less b/client/less/bootstrap/alerts.less new file mode 100755 index 0000000..3eab066 --- /dev/null +++ b/client/less/bootstrap/alerts.less @@ -0,0 +1,67 @@ +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: @alert-padding; + margin-bottom: @line-height-computed; + border: 1px solid transparent; + border-radius: @alert-border-radius; + + // Headings for larger alerts + h4 { + margin-top: 0; + // Specified for the h4 to prevent conflicts of changing @headings-color + color: inherit; + } + // Provide class for links that match alerts + .alert-link { + font-weight: @alert-link-font-weight; + } + + // Improve alignment and spacing of inner content + > p, + > ul { + margin-bottom: 0; + } + > p + p { + margin-top: 5px; + } +} + +// Dismissable alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissable { + padding-right: (@alert-padding + 20); + + // Adjust close link position + .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; + } +} + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +.alert-success { + .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); +} +.alert-info { + .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); +} +.alert-warning { + .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); +} +.alert-danger { + .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); +} diff --git a/client/less/bootstrap/badges.less b/client/less/bootstrap/badges.less new file mode 100755 index 0000000..0b69753 --- /dev/null +++ b/client/less/bootstrap/badges.less @@ -0,0 +1,51 @@ +// +// Badges +// -------------------------------------------------- + + +// Base classes +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: @font-size-small; + font-weight: @badge-font-weight; + color: @badge-color; + line-height: @badge-line-height; + vertical-align: baseline; + white-space: nowrap; + text-align: center; + background-color: @badge-bg; + border-radius: @badge-border-radius; + + // Empty badges collapse automatically (not available in IE8) + &:empty { + display: none; + } +} + +// Hover state, but only for links +a.badge { + &:hover, + &:focus { + color: @badge-link-hover-color; + text-decoration: none; + cursor: pointer; + } +} + +// Quick fix for labels/badges in buttons +.btn .badge { + position: relative; + top: -1px; +} + +// Account for counters in navs +a.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: @badge-active-color; + background-color: @badge-active-bg; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} diff --git a/client/less/bootstrap/bootstrap.less b/client/less/bootstrap/bootstrap.less new file mode 100755 index 0000000..b368b87 --- /dev/null +++ b/client/less/bootstrap/bootstrap.less @@ -0,0 +1,49 @@ +// Core variables and mixins +@import "variables.less"; +@import "mixins.less"; + +// Reset +@import "normalize.less"; +@import "print.less"; + +// Core CSS +@import "scaffolding.less"; +@import "type.less"; +@import "code.less"; +@import "grid.less"; +@import "tables.less"; +@import "forms.less"; +@import "buttons.less"; + +// Components +@import "component-animations.less"; +@import "glyphicons.less"; +@import "dropdowns.less"; +@import "button-groups.less"; +@import "input-groups.less"; +@import "navs.less"; +@import "navbar.less"; +@import "breadcrumbs.less"; +@import "pagination.less"; +@import "pager.less"; +@import "labels.less"; +@import "badges.less"; +@import "jumbotron.less"; +@import "thumbnails.less"; +@import "alerts.less"; +@import "progress-bars.less"; +@import "media.less"; +@import "list-group.less"; +@import "panels.less"; +@import "wells.less"; +@import "close.less"; + +// Components w/ JavaScript +@import "modals.less"; +@import "tooltip.less"; +@import "popovers.less"; +@import "carousel.less"; + +// Utility classes +@import "utilities.less"; +@import "responsive-utilities.less"; diff --git a/client/less/bootstrap/breadcrumbs.less b/client/less/bootstrap/breadcrumbs.less new file mode 100755 index 0000000..60b33ea --- /dev/null +++ b/client/less/bootstrap/breadcrumbs.less @@ -0,0 +1,23 @@ +// +// Breadcrumbs +// -------------------------------------------------- + + +.breadcrumb { + padding: 8px 15px; + margin-bottom: @line-height-computed; + list-style: none; + background-color: @breadcrumb-bg; + border-radius: @border-radius-base; + > li { + display: inline-block; + + li:before { + content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space + padding: 0 5px; + color: @breadcrumb-color; + } + } + > .active { + color: @breadcrumb-active-color; + } +} diff --git a/client/less/bootstrap/button-groups.less b/client/less/bootstrap/button-groups.less new file mode 100755 index 0000000..c253576 --- /dev/null +++ b/client/less/bootstrap/button-groups.less @@ -0,0 +1,253 @@ +// +// Button groups +// -------------------------------------------------- + +// Button carets +// +// Match the button text color to the arrow/caret for indicating dropdown-ness. + +.caret { + .btn-default & { + border-top-color: @btn-default-color; + } + .btn-primary &, + .btn-success &, + .btn-warning &, + .btn-danger &, + .btn-info & { + border-top-color: #fff; + } +} +.dropup { + .btn-default .caret { + border-bottom-color: @btn-default-color; + } + .btn-primary, + .btn-success, + .btn-warning, + .btn-danger, + .btn-info { + .caret { + border-bottom-color: #fff; + } + } +} + +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; // match .btn alignment given font-size hack above + > .btn { + position: relative; + float: left; + // Bring the "active" button to the front + &:hover, + &:focus, + &:active, + &.active { + z-index: 2; + } + &:focus { + // Remove focus outline when dropdown JS adds it after closing the menu + outline: none; + } + } +} + +// Prevent double borders when buttons are next to each other +.btn-group { + .btn + .btn, + .btn + .btn-group, + .btn-group + .btn, + .btn-group + .btn-group { + margin-left: -1px; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + .clearfix(); + + .btn-group { + float: left; + } + // Space out series of button groups + > .btn, + > .btn-group { + + .btn, + + .btn-group { + margin-left: 5px; + } + } +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + &:not(:last-child):not(.dropdown-toggle) { + .border-right-radius(0); + } +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + .border-left-radius(0); +} + +// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child { + > .btn:last-child, + > .dropdown-toggle { + .border-right-radius(0); + } +} +.btn-group > .btn-group:last-child > .btn:first-child { + .border-left-radius(0); +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-xs > .btn { .btn-xs(); } +.btn-group-sm > .btn { .btn-sm(); } +.btn-group-lg > .btn { .btn-lg(); } + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} + +// The clickable button for toggling the menu +// Remove the gradient and set the same inset shadow as the :active state +.btn-group.open .dropdown-toggle { + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + .box-shadow(none); + } +} + + +// Reposition the caret +.btn .caret { + margin-left: 0; +} +// Carets in other button sizes +.btn-lg .caret { + border-width: @caret-width-large @caret-width-large 0; + border-bottom-width: 0; +} +// Upside down carets for .dropup +.dropup .btn-lg .caret { + border-width: 0 @caret-width-large @caret-width-large; +} + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + > .btn, + > .btn-group { + display: block; + float: none; + width: 100%; + max-width: 100%; + } + + // Clear floats so dropdown menus can be properly placed + > .btn-group { + .clearfix(); + > .btn { + float: none; + } + } + + > .btn + .btn, + > .btn + .btn-group, + > .btn-group + .btn, + > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; + } +} + +.btn-group-vertical > .btn { + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + &:first-child:not(:last-child) { + border-top-right-radius: @border-radius-base; + .border-bottom-radius(0); + } + &:last-child:not(:first-child) { + border-bottom-left-radius: @border-radius-base; + .border-top-radius(0); + } +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child { + > .btn:last-child, + > .dropdown-toggle { + .border-bottom-radius(0); + } +} +.btn-group-vertical > .btn-group:last-child > .btn:first-child { + .border-top-radius(0); +} + + + +// Justified button groups +// ---------------------- + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; + .btn { + float: none; + display: table-cell; + width: 1%; + } +} + + +// Checkbox and radio options +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + display: none; +} diff --git a/client/less/bootstrap/buttons.less b/client/less/bootstrap/buttons.less new file mode 100755 index 0000000..a090960 --- /dev/null +++ b/client/less/bootstrap/buttons.less @@ -0,0 +1,158 @@ +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +// Core styles +.btn { + display: inline-block; + margin-bottom: 0; // For input.btn + font-weight: @btn-font-weight; + text-align: center; + vertical-align: middle; + cursor: pointer; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + white-space: nowrap; + .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base); + .user-select(none); + + &:focus { + .tab-focus(); + } + + &:hover, + &:focus { + color: @btn-default-color; + text-decoration: none; + } + + &:active, + &.active { + outline: 0; + background-image: none; + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + cursor: not-allowed; + pointer-events: none; // Future-proof disabling of clicks + .opacity(.65); + .box-shadow(none); + } + +} + + +// Alternate buttons +// -------------------------------------------------- + +.btn-default { + .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border); +} +.btn-primary { + .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border); +} +// Warning appears as orange +.btn-warning { + .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border); +} +// Danger and error appear as red +.btn-danger { + .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border); +} +// Success appears as green +.btn-success { + .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border); +} +// Info appears as blue-green +.btn-info { + .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border); +} + + +// Link buttons +// ------------------------- + +// Make a button look and behave like a link +.btn-link { + color: @link-color; + font-weight: normal; + cursor: pointer; + border-radius: 0; + + &, + &:active, + &[disabled], + fieldset[disabled] & { + background-color: transparent; + .box-shadow(none); + } + &, + &:hover, + &:focus, + &:active { + border-color: transparent; + } + &:hover, + &:focus { + color: @link-hover-color; + text-decoration: underline; + background-color: transparent; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @btn-link-disabled-color; + text-decoration: none; + } + } +} + + +// Button Sizes +// -------------------------------------------------- + +.btn-lg { + // line-height: ensure even-numbered height of button next to large input + .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); +} +.btn-sm, +.btn-xs { + // line-height: ensure proper height of button next to small input + .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small); +} +.btn-xs { + padding: 1px 5px; +} + + +// Block button +// -------------------------------------------------- + +.btn-block { + display: block; + width: 100%; + padding-left: 0; + padding-right: 0; +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} diff --git a/client/less/bootstrap/carousel.less b/client/less/bootstrap/carousel.less new file mode 100755 index 0000000..317963b --- /dev/null +++ b/client/less/bootstrap/carousel.less @@ -0,0 +1,231 @@ +// +// Carousel +// -------------------------------------------------- + + +// Wrapper for the slide container and indicators +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; + + > .item { + display: none; + position: relative; + .transition(.6s ease-in-out left); + + // Account for jankitude on images + > img, + > a > img { + .img-responsive(); + line-height: 1; + } + } + + > .active, + > .next, + > .prev { display: block; } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: @carousel-control-width; + .opacity(@carousel-control-opacity); + font-size: @carousel-control-font-size; + color: @carousel-control-color; + text-align: center; + text-shadow: @carousel-text-shadow; + // We can't have this transition here because WebKit cancels the carousel + // animation if you trip this while in the middle of another animation. + + // Set gradients for backgrounds + &.left { + #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001)); + } + &.right { + left: auto; + right: 0; + #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5)); + } + + // Hover/focus state + &:hover, + &:focus { + color: @carousel-control-color; + text-decoration: none; + .opacity(.9); + } + + // Toggles + .icon-prev, + .icon-next, + .glyphicon-chevron-left, + .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + } + .icon-prev, + .glyphicon-chevron-left { + left: 50%; + } + .icon-next, + .glyphicon-chevron-right { + right: 50%; + } + .icon-prev, + .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + font-family: serif; + } + + .icon-prev { + &:before { + content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) + } + } + .icon-next { + &:before { + content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) + } + } +} + +// Optional indicator pips +// +// Add an unordered list with the following class and add a list item for each +// slide your carousel holds. + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; + + li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid @carousel-indicator-border-color; + border-radius: 10px; + cursor: pointer; + + // IE8-9 hack for event handling + // + // Internet Explorer 8-9 does not support clicks on elements without a set + // `background-color`. We cannot use `filter` since that's not viewed as a + // background color by the browser. Thus, a hack is needed. + // + // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we + // set alpha transparency for the best results possible. + background-color: #000 \9; // IE8 + background-color: rgba(0,0,0,0); // IE9 + } + .active { + margin: 0; + width: 12px; + height: 12px; + background-color: @carousel-indicator-active-bg; + } +} + +// Optional captions +// ----------------------------- +// Hidden by default for smaller viewports +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: @carousel-caption-color; + text-align: center; + text-shadow: @carousel-text-shadow; + & .btn { + text-shadow: none; // No shadow for button elements in carousel-caption + } +} + + +// Scale up controls for tablets and up +@media screen and (min-width: @screen-sm-min) { + + // Scale up the controls a smidge + .carousel-control { + .glyphicons-chevron-left, + .glyphicons-chevron-right, + .icon-prev, + .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + margin-left: -15px; + font-size: 30px; + } + } + + // Show and left align the captions + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + + // Move up the indicators + .carousel-indicators { + bottom: 20px; + } +} diff --git a/client/less/bootstrap/close.less b/client/less/bootstrap/close.less new file mode 100755 index 0000000..9b4e74f --- /dev/null +++ b/client/less/bootstrap/close.less @@ -0,0 +1,33 @@ +// +// Close icons +// -------------------------------------------------- + + +.close { + float: right; + font-size: (@font-size-base * 1.5); + font-weight: @close-font-weight; + line-height: 1; + color: @close-color; + text-shadow: @close-text-shadow; + .opacity(.2); + + &:hover, + &:focus { + color: @close-color; + text-decoration: none; + cursor: pointer; + .opacity(.5); + } + + // Additional properties for button version + // iOS requires the button element instead of an anchor tag. + // If you want the anchor version, it requires `href="#"`. + button& { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; + } +} diff --git a/client/less/bootstrap/code.less b/client/less/bootstrap/code.less new file mode 100755 index 0000000..44e9e89 --- /dev/null +++ b/client/less/bootstrap/code.less @@ -0,0 +1,53 @@ +// +// Code (inline and block) +// -------------------------------------------------- + + +// Inline and block code styles +code, +kbd, +pre, +samp { + font-family: @font-family-monospace; +} + +// Inline code +code { + padding: 2px 4px; + font-size: 90%; + color: @code-color; + background-color: @code-bg; + white-space: nowrap; + border-radius: @border-radius-base; +} + +// Blocks of code +pre { + display: block; + padding: ((@line-height-computed - 1) / 2); + margin: 0 0 (@line-height-computed / 2); + font-size: (@font-size-base - 1); // 14px to 13px + line-height: @line-height-base; + word-break: break-all; + word-wrap: break-word; + color: @pre-color; + background-color: @pre-bg; + border: 1px solid @pre-border-color; + border-radius: @border-radius-base; + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: @pre-scrollable-max-height; + overflow-y: scroll; +} diff --git a/client/less/bootstrap/component-animations.less b/client/less/bootstrap/component-animations.less new file mode 100755 index 0000000..1efe45e --- /dev/null +++ b/client/less/bootstrap/component-animations.less @@ -0,0 +1,29 @@ +// +// Component animations +// -------------------------------------------------- + +// Heads up! +// +// We don't use the `.opacity()` mixin here since it causes a bug with text +// fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552. + +.fade { + opacity: 0; + .transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + display: none; + &.in { + display: block; + } +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + .transition(height .35s ease); +} diff --git a/client/less/bootstrap/dropdowns.less b/client/less/bootstrap/dropdowns.less new file mode 100755 index 0000000..5d7e0fb --- /dev/null +++ b/client/less/bootstrap/dropdowns.less @@ -0,0 +1,192 @@ +// +// Dropdown menus +// -------------------------------------------------- + + +// Dropdown arrow/caret +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: @caret-width-base solid @dropdown-caret-color; + border-right: @caret-width-base solid transparent; + border-left: @caret-width-base solid transparent; + // Firefox fix for https://github.com/twbs/bootstrap/issues/9538. Once fixed, + // we can just straight up remove this. + border-bottom: 0 dotted; +} + +// The dropdown wrapper (div) +.dropdown { + position: relative; +} + +// Prevent the focus on the dropdown toggle when closing dropdowns +.dropdown-toggle:focus { + outline: 0; +} + +// The dropdown menu (ul) +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: @zindex-dropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + font-size: @font-size-base; + background-color: @dropdown-bg; + border: 1px solid @dropdown-fallback-border; // IE8 fallback + border: 1px solid @dropdown-border; + border-radius: @border-radius-base; + .box-shadow(0 6px 12px rgba(0,0,0,.175)); + background-clip: padding-box; + + // Aligns the dropdown menu to right + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + .nav-divider(@dropdown-divider-bg); + } + + // Links within the dropdown menu + > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: @line-height-base; + color: @dropdown-link-color; + white-space: nowrap; // prevent links from randomly breaking onto new lines + } +} + +// Hover/Focus state +.dropdown-menu > li > a { + &:hover, + &:focus { + text-decoration: none; + color: @dropdown-link-hover-color; + background-color: @dropdown-link-hover-bg; + } +} + +// Active state +.dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: @dropdown-link-active-color; + text-decoration: none; + outline: 0; + background-color: @dropdown-link-active-bg; + } +} + +// Disabled state +// +// Gray out text and ensure the hover/focus state remains gray + +.dropdown-menu > .disabled > a { + &, + &:hover, + &:focus { + color: @dropdown-link-disabled-color; + } +} +// Nuke hover/focus effects +.dropdown-menu > .disabled > a { + &:hover, + &:focus { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + .reset-filter(); + cursor: not-allowed; + } +} + +// Open state for the dropdown +.open { + // Show the menu + > .dropdown-menu { + display: block; + } + + // Remove the outline when :focus is triggered + > a { + outline: 0; + } +} + +// Dropdown section headers +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: @font-size-small; + line-height: @line-height-base; + color: @dropdown-header-color; +} + +// Backdrop to catch body clicks on mobile, etc. +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: @zindex-dropdown - 10; +} + +// Right aligned dropdowns +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? + +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + // Firefox fix for https://github.com/twbs/bootstrap/issues/9538. Once this + // gets fixed, restore `border-top: 0;`. + border-top: 0 dotted; + border-bottom: @caret-width-base solid @dropdown-caret-color; + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; + } +} + + +// Component alignment +// +// Reiterate per navbar.less and the modified component alignment there. + +@media (min-width: @grid-float-breakpoint) { + .navbar-right { + .dropdown-menu { + .pull-right > .dropdown-menu(); + } + } +} + diff --git a/client/less/bootstrap/forms.less b/client/less/bootstrap/forms.less new file mode 100755 index 0000000..a74babd --- /dev/null +++ b/client/less/bootstrap/forms.less @@ -0,0 +1,364 @@ +// +// Forms +// -------------------------------------------------- + + +// Normalize non-controls +// +// Restyle and baseline non-control form elements. + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @line-height-computed; + font-size: (@font-size-base * 1.5); + line-height: inherit; + color: @legend-color; + border: 0; + border-bottom: 1px solid @legend-border-color; +} + +label { + display: inline-block; + margin-bottom: 5px; + font-weight: bold; +} + + +// Normalize form controls + +// Override content-box in Normalize (* isn't specific enough) +input[type="search"] { + .box-sizing(border-box); +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; /* IE8-9 */ + line-height: normal; +} + +// Set the height of select and file controls to match text inputs +input[type="file"] { + display: block; +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Fix optgroup Firefox bug per https://github.com/twbs/bootstrap/issues/7611 +select optgroup { + font-size: inherit; + font-style: inherit; + font-family: inherit; +} + +// Focus for select, file, radio, and checkbox +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + +// Fix for Chrome number input +// Setting certain font-sizes causes the `I` bar to appear on hover of the bottom increment button. +// See https://github.com/twbs/bootstrap/issues/8350 for more. +input[type="number"] { + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + height: auto; + } +} + +// Adjust output element +output { + display: block; + padding-top: (@padding-base-vertical + 1); + font-size: @font-size-base; + line-height: @line-height-base; + color: @input-color; + vertical-align: middle; +} + + +// Common form controls +// +// Shared size and type resets for form controls. Apply `.form-control` to any +// of the following form controls: +// +// select +// textarea +// input[type="text"] +// input[type="password"] +// input[type="datetime"] +// input[type="datetime-local"] +// input[type="date"] +// input[type="month"] +// input[type="time"] +// input[type="week"] +// input[type="number"] +// input[type="email"] +// input[type="url"] +// input[type="search"] +// input[type="tel"] +// input[type="color"] + +.form-control { + display: block; + width: 100%; + height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + padding: @padding-base-vertical @padding-base-horizontal; + font-size: @font-size-base; + line-height: @line-height-base; + color: @input-color; + vertical-align: middle; + background-color: @input-bg; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid @input-border; + border-radius: @input-border-radius; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s"); + + // Customize the `:focus` state to imitate native WebKit styles. + .form-control-focus(); + + // Placeholder + // + // Placeholder text gets special styles because when browsers invalidate entire + // lines if it doesn't understand a selector/ + .placeholder(); + + // Disabled and read-only inputs + // Note: HTML5 says that controls under a fieldset > legend:first-child won't + // be disabled if the fieldset is disabled. Due to implementation difficulty, + // we don't honor that edge case; we style them as disabled anyway. + &[disabled], + &[readonly], + fieldset[disabled] & { + cursor: not-allowed; + background-color: @input-bg-disabled; + } + + // Reset height for `textarea`s + textarea& { + height: auto; + } +} + + +// Form groups +// +// Designed to help with the organization and spacing of vertical forms. For +// horizontal forms, use the predefined grid classes. + +.form-group { + margin-bottom: 15px; +} + + +// Checkboxes and radios +// +// Indent the labels to position radios/checkboxes as hanging controls. + +.radio, +.checkbox { + display: block; + min-height: @line-height-computed; // clear the floating input if there is no label text + margin-top: 10px; + margin-bottom: 10px; + padding-left: 20px; + vertical-align: middle; + label { + display: inline; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; + } +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing +} + +// Radios and checkboxes on same line +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; // space out consecutive inline controls +} + +// Apply same disabled cursor tweak as for inputs +// +// Note: Neither radios nor checkboxes can be readonly. +input[type="radio"], +input[type="checkbox"], +.radio, +.radio-inline, +.checkbox, +.checkbox-inline { + &[disabled], + fieldset[disabled] & { + cursor: not-allowed; + } +} + +// Form control sizing +.input-sm { + .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small); +} + +.input-lg { + .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); +} + + +// Form control feedback states +// +// Apply contextual and semantic states to individual form controls. + +// Warning +.has-warning { + .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg); +} +// Error +.has-error { + .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg); +} +// Success +.has-success { + .form-control-validation(@state-success-text; @state-success-text; @state-success-bg); +} + + +// Static form control text +// +// Apply class to a `p` element to make any string of text align with labels in +// a horizontal form layout. + +.form-control-static { + margin-bottom: 0; // Remove default margin from `p` +} + + +// Help text +// +// Apply to any element you wish to create light text for placement immediately +// below a form control. Use for general help, formatting, or instructional text. + +.help-block { + display: block; // account for any element using help-block + margin-top: 5px; + margin-bottom: 10px; + color: lighten(@text-color, 25%); // lighten the text some for contrast +} + + + +// Inline forms +// +// Make forms appear inline(-block) by adding the `.form-inline` class. Inline +// forms begin stacked on extra small (mobile) devices and then go inline when +// viewports reach <768px. +// +// Requires wrapping inputs and labels with `.form-group` for proper display of +// default HTML form controls and our custom form controls (e.g., input groups). +// +// Heads up! This is mixin-ed into `.navbar-form` in navbars.less. + +.form-inline { + + // Kick in the inline + @media (min-width: @screen-sm) { + // Inline-block all the things for "inline" + .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + + // In navbar-form, allow folks to *not* use `.form-group` + .form-control { + display: inline-block; + } + + // Remove default margin on radios/checkboxes that were used for stacking, and + // then undo the floating of radios and checkboxes to match (which also avoids + // a bug in WebKit: https://github.com/twbs/bootstrap/issues/1969). + .radio, + .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + padding-left: 0; + } + .radio input[type="radio"], + .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } + } +} + + +// Horizontal forms +// +// Horizontal forms are built on grid classes and allow you to create forms with +// labels on the left and inputs on the right. + +.form-horizontal { + + // Consistent vertical alignment of labels, radios, and checkboxes + .control-label, + .radio, + .checkbox, + .radio-inline, + .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: (@padding-base-vertical + 1); // Default padding plus a border + } + + // Make form groups behave like rows + .form-group { + .make-row(); + } + + .form-control-static { + padding-top: (@padding-base-vertical + 1); + } + + // Only right align form labels here when the columns stop stacking + @media (min-width: @screen-sm-min) { + .control-label { + text-align: right; + } + } +} diff --git a/client/less/bootstrap/glyphicons.less b/client/less/bootstrap/glyphicons.less new file mode 100755 index 0000000..9de2dd3 --- /dev/null +++ b/client/less/bootstrap/glyphicons.less @@ -0,0 +1,237 @@ +// +// Glyphicons for Bootstrap +// +// Since icons are fonts, they can be placed anywhere text is placed and are +// thus automatically sized to match the surrounding child. To use, create an +// inline element with the appropriate classes, like so: +// +// Star + +// Import the fonts +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('@{icon-font-path}@{icon-font-name}.eot'); + src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'), + url('@{icon-font-path}@{icon-font-name}.woff') format('woff'), + url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'), + url('@{icon-font-path}@{icon-font-name}.svg#glyphicons_halflingsregular') format('svg'); +} + +// Catchall baseclass +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + &:empty { + width: 1em; + } +} + +// Individual icons +.glyphicon-asterisk { &:before { content: "\2a"; } } +.glyphicon-plus { &:before { content: "\2b"; } } +.glyphicon-euro { &:before { content: "\20ac"; } } +.glyphicon-minus { &:before { content: "\2212"; } } +.glyphicon-cloud { &:before { content: "\2601"; } } +.glyphicon-envelope { &:before { content: "\2709"; } } +.glyphicon-pencil { &:before { content: "\270f"; } } +.glyphicon-glass { &:before { content: "\e001"; } } +.glyphicon-music { &:before { content: "\e002"; } } +.glyphicon-search { &:before { content: "\e003"; } } +.glyphicon-heart { &:before { content: "\e005"; } } +.glyphicon-star { &:before { content: "\e006"; } } +.glyphicon-star-empty { &:before { content: "\e007"; } } +.glyphicon-user { &:before { content: "\e008"; } } +.glyphicon-film { &:before { content: "\e009"; } } +.glyphicon-th-large { &:before { content: "\e010"; } } +.glyphicon-th { &:before { content: "\e011"; } } +.glyphicon-th-list { &:before { content: "\e012"; } } +.glyphicon-ok { &:before { content: "\e013"; } } +.glyphicon-remove { &:before { content: "\e014"; } } +.glyphicon-zoom-in { &:before { content: "\e015"; } } +.glyphicon-zoom-out { &:before { content: "\e016"; } } +.glyphicon-off { &:before { content: "\e017"; } } +.glyphicon-signal { &:before { content: "\e018"; } } +.glyphicon-cog { &:before { content: "\e019"; } } +.glyphicon-trash { &:before { content: "\e020"; } } +.glyphicon-home { &:before { content: "\e021"; } } +.glyphicon-file { &:before { content: "\e022"; } } +.glyphicon-time { &:before { content: "\e023"; } } +.glyphicon-road { &:before { content: "\e024"; } } +.glyphicon-download-alt { &:before { content: "\e025"; } } +.glyphicon-download { &:before { content: "\e026"; } } +.glyphicon-upload { &:before { content: "\e027"; } } +.glyphicon-inbox { &:before { content: "\e028"; } } +.glyphicon-play-circle { &:before { content: "\e029"; } } +.glyphicon-repeat { &:before { content: "\e030"; } } +.glyphicon-refresh { &:before { content: "\e031"; } } +.glyphicon-list-alt { &:before { content: "\e032"; } } +.glyphicon-lock { &:before { content: "\e033"; } } +.glyphicon-flag { &:before { content: "\e034"; } } +.glyphicon-headphones { &:before { content: "\e035"; } } +.glyphicon-volume-off { &:before { content: "\e036"; } } +.glyphicon-volume-down { &:before { content: "\e037"; } } +.glyphicon-volume-up { &:before { content: "\e038"; } } +.glyphicon-qrcode { &:before { content: "\e039"; } } +.glyphicon-barcode { &:before { content: "\e040"; } } +.glyphicon-tag { &:before { content: "\e041"; } } +.glyphicon-tags { &:before { content: "\e042"; } } +.glyphicon-book { &:before { content: "\e043"; } } +.glyphicon-bookmark { &:before { content: "\e044"; } } +.glyphicon-print { &:before { content: "\e045"; } } +.glyphicon-camera { &:before { content: "\e046"; } } +.glyphicon-font { &:before { content: "\e047"; } } +.glyphicon-bold { &:before { content: "\e048"; } } +.glyphicon-italic { &:before { content: "\e049"; } } +.glyphicon-text-height { &:before { content: "\e050"; } } +.glyphicon-text-width { &:before { content: "\e051"; } } +.glyphicon-align-left { &:before { content: "\e052"; } } +.glyphicon-align-center { &:before { content: "\e053"; } } +.glyphicon-align-right { &:before { content: "\e054"; } } +.glyphicon-align-justify { &:before { content: "\e055"; } } +.glyphicon-list { &:before { content: "\e056"; } } +.glyphicon-indent-left { &:before { content: "\e057"; } } +.glyphicon-indent-right { &:before { content: "\e058"; } } +.glyphicon-facetime-video { &:before { content: "\e059"; } } +.glyphicon-picture { &:before { content: "\e060"; } } +.glyphicon-map-marker { &:before { content: "\e062"; } } +.glyphicon-adjust { &:before { content: "\e063"; } } +.glyphicon-tint { &:before { content: "\e064"; } } +.glyphicon-edit { &:before { content: "\e065"; } } +.glyphicon-share { &:before { content: "\e066"; } } +.glyphicon-check { &:before { content: "\e067"; } } +.glyphicon-move { &:before { content: "\e068"; } } +.glyphicon-step-backward { &:before { content: "\e069"; } } +.glyphicon-fast-backward { &:before { content: "\e070"; } } +.glyphicon-backward { &:before { content: "\e071"; } } +.glyphicon-play { &:before { content: "\e072"; } } +.glyphicon-pause { &:before { content: "\e073"; } } +.glyphicon-stop { &:before { content: "\e074"; } } +.glyphicon-forward { &:before { content: "\e075"; } } +.glyphicon-fast-forward { &:before { content: "\e076"; } } +.glyphicon-step-forward { &:before { content: "\e077"; } } +.glyphicon-eject { &:before { content: "\e078"; } } +.glyphicon-chevron-left { &:before { content: "\e079"; } } +.glyphicon-chevron-right { &:before { content: "\e080"; } } +.glyphicon-plus-sign { &:before { content: "\e081"; } } +.glyphicon-minus-sign { &:before { content: "\e082"; } } +.glyphicon-remove-sign { &:before { content: "\e083"; } } +.glyphicon-ok-sign { &:before { content: "\e084"; } } +.glyphicon-question-sign { &:before { content: "\e085"; } } +.glyphicon-info-sign { &:before { content: "\e086"; } } +.glyphicon-screenshot { &:before { content: "\e087"; } } +.glyphicon-remove-circle { &:before { content: "\e088"; } } +.glyphicon-ok-circle { &:before { content: "\e089"; } } +.glyphicon-ban-circle { &:before { content: "\e090"; } } +.glyphicon-arrow-left { &:before { content: "\e091"; } } +.glyphicon-arrow-right { &:before { content: "\e092"; } } +.glyphicon-arrow-up { &:before { content: "\e093"; } } +.glyphicon-arrow-down { &:before { content: "\e094"; } } +.glyphicon-share-alt { &:before { content: "\e095"; } } +.glyphicon-resize-full { &:before { content: "\e096"; } } +.glyphicon-resize-small { &:before { content: "\e097"; } } +.glyphicon-exclamation-sign { &:before { content: "\e101"; } } +.glyphicon-gift { &:before { content: "\e102"; } } +.glyphicon-leaf { &:before { content: "\e103"; } } +.glyphicon-fire { &:before { content: "\e104"; } } +.glyphicon-eye-open { &:before { content: "\e105"; } } +.glyphicon-eye-close { &:before { content: "\e106"; } } +.glyphicon-warning-sign { &:before { content: "\e107"; } } +.glyphicon-plane { &:before { content: "\e108"; } } +.glyphicon-calendar { &:before { content: "\e109"; } } +.glyphicon-random { &:before { content: "\e110"; } } +.glyphicon-comment { &:before { content: "\e111"; } } +.glyphicon-magnet { &:before { content: "\e112"; } } +.glyphicon-chevron-up { &:before { content: "\e113"; } } +.glyphicon-chevron-down { &:before { content: "\e114"; } } +.glyphicon-retweet { &:before { content: "\e115"; } } +.glyphicon-shopping-cart { &:before { content: "\e116"; } } +.glyphicon-folder-close { &:before { content: "\e117"; } } +.glyphicon-folder-open { &:before { content: "\e118"; } } +.glyphicon-resize-vertical { &:before { content: "\e119"; } } +.glyphicon-resize-horizontal { &:before { content: "\e120"; } } +.glyphicon-hdd { &:before { content: "\e121"; } } +.glyphicon-bullhorn { &:before { content: "\e122"; } } +.glyphicon-bell { &:before { content: "\e123"; } } +.glyphicon-certificate { &:before { content: "\e124"; } } +.glyphicon-thumbs-up { &:before { content: "\e125"; } } +.glyphicon-thumbs-down { &:before { content: "\e126"; } } +.glyphicon-hand-right { &:before { content: "\e127"; } } +.glyphicon-hand-left { &:before { content: "\e128"; } } +.glyphicon-hand-up { &:before { content: "\e129"; } } +.glyphicon-hand-down { &:before { content: "\e130"; } } +.glyphicon-circle-arrow-right { &:before { content: "\e131"; } } +.glyphicon-circle-arrow-left { &:before { content: "\e132"; } } +.glyphicon-circle-arrow-up { &:before { content: "\e133"; } } +.glyphicon-circle-arrow-down { &:before { content: "\e134"; } } +.glyphicon-globe { &:before { content: "\e135"; } } +.glyphicon-wrench { &:before { content: "\e136"; } } +.glyphicon-tasks { &:before { content: "\e137"; } } +.glyphicon-filter { &:before { content: "\e138"; } } +.glyphicon-briefcase { &:before { content: "\e139"; } } +.glyphicon-fullscreen { &:before { content: "\e140"; } } +.glyphicon-dashboard { &:before { content: "\e141"; } } +.glyphicon-paperclip { &:before { content: "\e142"; } } +.glyphicon-heart-empty { &:before { content: "\e143"; } } +.glyphicon-link { &:before { content: "\e144"; } } +.glyphicon-phone { &:before { content: "\e145"; } } +.glyphicon-pushpin { &:before { content: "\e146"; } } +.glyphicon-usd { &:before { content: "\e148"; } } +.glyphicon-gbp { &:before { content: "\e149"; } } +.glyphicon-sort { &:before { content: "\e150"; } } +.glyphicon-sort-by-alphabet { &:before { content: "\e151"; } } +.glyphicon-sort-by-alphabet-alt { &:before { content: "\e152"; } } +.glyphicon-sort-by-order { &:before { content: "\e153"; } } +.glyphicon-sort-by-order-alt { &:before { content: "\e154"; } } +.glyphicon-sort-by-attributes { &:before { content: "\e155"; } } +.glyphicon-sort-by-attributes-alt { &:before { content: "\e156"; } } +.glyphicon-unchecked { &:before { content: "\e157"; } } +.glyphicon-expand { &:before { content: "\e158"; } } +.glyphicon-collapse-down { &:before { content: "\e159"; } } +.glyphicon-collapse-up { &:before { content: "\e160"; } } +.glyphicon-log-in { &:before { content: "\e161"; } } +.glyphicon-flash { &:before { content: "\e162"; } } +.glyphicon-log-out { &:before { content: "\e163"; } } +.glyphicon-new-window { &:before { content: "\e164"; } } +.glyphicon-record { &:before { content: "\e165"; } } +.glyphicon-save { &:before { content: "\e166"; } } +.glyphicon-open { &:before { content: "\e167"; } } +.glyphicon-saved { &:before { content: "\e168"; } } +.glyphicon-import { &:before { content: "\e169"; } } +.glyphicon-export { &:before { content: "\e170"; } } +.glyphicon-send { &:before { content: "\e171"; } } +.glyphicon-floppy-disk { &:before { content: "\e172"; } } +.glyphicon-floppy-saved { &:before { content: "\e173"; } } +.glyphicon-floppy-remove { &:before { content: "\e174"; } } +.glyphicon-floppy-save { &:before { content: "\e175"; } } +.glyphicon-floppy-open { &:before { content: "\e176"; } } +.glyphicon-credit-card { &:before { content: "\e177"; } } +.glyphicon-transfer { &:before { content: "\e178"; } } +.glyphicon-cutlery { &:before { content: "\e179"; } } +.glyphicon-header { &:before { content: "\e180"; } } +.glyphicon-compressed { &:before { content: "\e181"; } } +.glyphicon-earphone { &:before { content: "\e182"; } } +.glyphicon-phone-alt { &:before { content: "\e183"; } } +.glyphicon-tower { &:before { content: "\e184"; } } +.glyphicon-stats { &:before { content: "\e185"; } } +.glyphicon-sd-video { &:before { content: "\e186"; } } +.glyphicon-hd-video { &:before { content: "\e187"; } } +.glyphicon-subtitles { &:before { content: "\e188"; } } +.glyphicon-sound-stereo { &:before { content: "\e189"; } } +.glyphicon-sound-dolby { &:before { content: "\e190"; } } +.glyphicon-sound-5-1 { &:before { content: "\e191"; } } +.glyphicon-sound-6-1 { &:before { content: "\e192"; } } +.glyphicon-sound-7-1 { &:before { content: "\e193"; } } +.glyphicon-copyright-mark { &:before { content: "\e194"; } } +.glyphicon-registration-mark { &:before { content: "\e195"; } } +.glyphicon-cloud-download { &:before { content: "\e197"; } } +.glyphicon-cloud-upload { &:before { content: "\e198"; } } +.glyphicon-tree-conifer { &:before { content: "\e199"; } } +.glyphicon-tree-deciduous { &:before { content: "\e200"; } } diff --git a/client/less/bootstrap/grid.less b/client/less/bootstrap/grid.less new file mode 100755 index 0000000..67e78f7 --- /dev/null +++ b/client/less/bootstrap/grid.less @@ -0,0 +1,93 @@ +// +// Grid system +// -------------------------------------------------- + +// Set the container width, and override it for fixed navbars in media queries +.container { + .container-fixed(); +} + +// mobile first defaults +.row { + .make-row(); +} + +// Common styles for small and large grid columns +.make-grid-columns(); + + +// Extra small grid +// +// Grid classes for extra small devices like smartphones. No offset, push, or +// pull classes are present here due to the size of the target. +// +// Note that `.col-xs-12` doesn't get floated on purpose--there's no need since +// it's full-width. + +.make-grid-columns-float(xs); +.make-grid(@grid-columns, xs, width); +.make-grid(@grid-columns, xs, pull); +.make-grid(@grid-columns, xs, push); +.make-grid(@grid-columns, xs, offset); + + +// Small grid +// +// Columns, offsets, pushes, and pulls for the small device range, from phones +// to tablets. +// +// Note that `.col-sm-12` doesn't get floated on purpose--there's no need since +// it's full-width. + +@media (min-width: @screen-sm-min) { + .container { + width: @container-sm; + } + + .make-grid-columns-float(sm); + .make-grid(@grid-columns, sm, width); + .make-grid(@grid-columns, sm, pull); + .make-grid(@grid-columns, sm, push); + .make-grid(@grid-columns, sm, offset); +} + + +// Medium grid +// +// Columns, offsets, pushes, and pulls for the desktop device range. +// +// Note that `.col-md-12` doesn't get floated on purpose--there's no need since +// it's full-width. + +@media (min-width: @screen-md-min) { + .container { + width: @container-md; + } + + .make-grid-columns-float(md); + .make-grid(@grid-columns, md, width); + .make-grid(@grid-columns, md, pull); + .make-grid(@grid-columns, md, push); + .make-grid(@grid-columns, md, offset); +} + + +// Large grid +// +// Columns, offsets, pushes, and pulls for the large desktop device range. +// +// Note that `.col-lg-12` doesn't get floated on purpose--there's no need since +// it's full-width. + +@media (min-width: @screen-lg-min) { + .container { + width: @container-lg; + } + + .make-grid-columns-float(lg); + .make-grid(@grid-columns, lg, width); + .make-grid(@grid-columns, lg, pull); + .make-grid(@grid-columns, lg, push); + .make-grid(@grid-columns, lg, offset); +} + diff --git a/client/less/bootstrap/input-groups.less b/client/less/bootstrap/input-groups.less new file mode 100755 index 0000000..8516a79 --- /dev/null +++ b/client/less/bootstrap/input-groups.less @@ -0,0 +1,136 @@ +// +// Input groups +// -------------------------------------------------- + +// Base styles +// ------------------------- +.input-group { + position: relative; // For dropdowns + display: table; + border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table + + // Undo padding and float of grid classes + &.col { + float: none; + padding-left: 0; + padding-right: 0; + } + + .form-control { + width: 100%; + margin-bottom: 0; + } +} + +// Sizing options +// +// Remix the default form control sizing classes into new ones for easier +// manipulation. + +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { .input-lg(); } +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { .input-sm(); } + + +// Display as table-cell +// ------------------------- +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; + + &:not(:first-child):not(:last-child) { + border-radius: 0; + } +} +// Addon and addon wrapper for buttons +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; // Match the inputs +} + +// Text input groups +// ------------------------- +.input-group-addon { + padding: @padding-base-vertical @padding-base-horizontal; + font-size: @font-size-base; + font-weight: normal; + line-height: 1; + color: @input-color; + text-align: center; + background-color: @input-group-addon-bg; + border: 1px solid @input-group-addon-border-color; + border-radius: @border-radius-base; + + // Sizing + &.input-sm { + padding: @padding-small-vertical @padding-small-horizontal; + font-size: @font-size-small; + border-radius: @border-radius-small; + } + &.input-lg { + padding: @padding-large-vertical @padding-large-horizontal; + font-size: @font-size-large; + border-radius: @border-radius-large; + } + + // Nuke default margins from checkboxes and radios to vertically center within. + input[type="radio"], + input[type="checkbox"] { + margin-top: 0; + } +} + +// Reset rounded corners +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { + .border-right-radius(0); +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child) { + .border-left-radius(0); +} +.input-group-addon:last-child { + border-left: 0; +} + +// Button input groups +// ------------------------- +.input-group-btn { + position: relative; + white-space: nowrap; + + // Negative margin to only have a 1px border between the two + &:first-child > .btn { + margin-right: -1px; + } + &:last-child > .btn { + margin-left: -1px; + } +} +.input-group-btn > .btn { + position: relative; + // Jankily prevent input button groups from wrapping + + .btn { + margin-left: -4px; + } + // Bring the "active" button to the front + &:hover, + &:active { + z-index: 2; + } +} diff --git a/client/less/bootstrap/jumbotron.less b/client/less/bootstrap/jumbotron.less new file mode 100755 index 0000000..22c2978 --- /dev/null +++ b/client/less/bootstrap/jumbotron.less @@ -0,0 +1,40 @@ +// +// Jumbotron +// -------------------------------------------------- + + +.jumbotron { + padding: @jumbotron-padding; + margin-bottom: @jumbotron-padding; + font-size: @jumbotron-font-size; + font-weight: 200; + line-height: (@line-height-base * 1.5); + color: @jumbotron-color; + background-color: @jumbotron-bg; + + h1 { + line-height: 1; + color: @jumbotron-heading-color; + } + p { + line-height: 1.4; + } + + .container & { + border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container + } + + @media screen and (min-width: @screen-sm-min) { + padding-top: (@jumbotron-padding * 1.6); + padding-bottom: (@jumbotron-padding * 1.6); + + .container & { + padding-left: (@jumbotron-padding * 2); + padding-right: (@jumbotron-padding * 2); + } + + h1 { + font-size: (@font-size-base * 4.5); + } + } +} diff --git a/client/less/bootstrap/labels.less b/client/less/bootstrap/labels.less new file mode 100755 index 0000000..cad5ce5 --- /dev/null +++ b/client/less/bootstrap/labels.less @@ -0,0 +1,58 @@ +// +// Labels +// -------------------------------------------------- + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: @label-color; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + + // Add hover effects, but only for links + &[href] { + &:hover, + &:focus { + color: @label-link-hover-color; + text-decoration: none; + cursor: pointer; + } + } + + // Empty labels collapse automatically (not available in IE8) + &:empty { + display: none; + } +} + +// Colors +// Contextual variations (linked labels get darker on :hover) + +.label-default { + .label-variant(@label-default-bg); +} + +.label-primary { + .label-variant(@label-primary-bg); +} + +.label-success { + .label-variant(@label-success-bg); +} + +.label-info { + .label-variant(@label-info-bg); +} + +.label-warning { + .label-variant(@label-warning-bg); +} + +.label-danger { + .label-variant(@label-danger-bg); +} diff --git a/client/less/bootstrap/list-group.less b/client/less/bootstrap/list-group.less new file mode 100755 index 0000000..2cee529 --- /dev/null +++ b/client/less/bootstrap/list-group.less @@ -0,0 +1,88 @@ +// +// List groups +// -------------------------------------------------- + +// Base class +// +// Easily usable on
    ,
      , or
      . +.list-group { + // No need to set list-style: none; since .list-group-item is block level + margin-bottom: 20px; + padding-left: 0; // reset padding because ul and ol +} + +// Individual list items +// ------------------------- + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + // Place the border on the list items and negative margin up for better styling + margin-bottom: -1px; + background-color: @list-group-bg; + border: 1px solid @list-group-border; + + // Round the first and last items + &:first-child { + .border-top-radius(@list-group-border-radius); + } + &:last-child { + margin-bottom: 0; + .border-bottom-radius(@list-group-border-radius); + } + + // Align badges within list items + > .badge { + float: right; + } + > .badge + .badge { + margin-right: 5px; + } +} + +// Linked list items +a.list-group-item { + color: @list-group-link-color; + + .list-group-item-heading { + color: @list-group-link-heading-color; + } + + // Hover state + &:hover, + &:focus { + text-decoration: none; + background-color: @list-group-hover-bg; + } + + // Active class on item itself, not parent + &.active, + &.active:hover, + &.active:focus { + z-index: 2; // Place active items above their siblings for proper border styling + color: @list-group-active-color; + background-color: @list-group-active-bg; + border-color: @list-group-active-border; + + // Force color to inherit for custom content + .list-group-item-heading { + color: inherit; + } + .list-group-item-text { + color: lighten(@list-group-active-bg, 40%); + } + } +} + +// Custom content options +// ------------------------- + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} diff --git a/client/less/bootstrap/media.less b/client/less/bootstrap/media.less new file mode 100755 index 0000000..5ad22cd --- /dev/null +++ b/client/less/bootstrap/media.less @@ -0,0 +1,56 @@ +// Media objects +// Source: http://stubbornella.org/content/?p=497 +// -------------------------------------------------- + + +// Common styles +// ------------------------- + +// Clear the floats +.media, +.media-body { + overflow: hidden; + zoom: 1; +} + +// Proper spacing between instances of .media +.media, +.media .media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} + +// For images and videos, set to block +.media-object { + display: block; +} + +// Reset margins on headings for tighter default spacing +.media-heading { + margin: 0 0 5px; +} + + +// Media image alignment +// ------------------------- + +.media { + > .pull-left { + margin-right: 10px; + } + > .pull-right { + margin-left: 10px; + } +} + + +// Media list variation +// ------------------------- + +// Undo default ul/ol styles +.media-list { + padding-left: 0; + list-style: none; +} diff --git a/client/less/bootstrap/mixins.less b/client/less/bootstrap/mixins.less new file mode 100755 index 0000000..3d24e66 --- /dev/null +++ b/client/less/bootstrap/mixins.less @@ -0,0 +1,858 @@ +// +// Mixins +// -------------------------------------------------- + + +// Utilities +// ------------------------- + +// Clearfix +// Source: http://nicolasgallagher.com/micro-clearfix-hack/ +// +// For modern browsers +// 1. The space content is one way to avoid an Opera bug when the +// contenteditable attribute is included anywhere else in the document. +// Otherwise it causes space to appear at the top and bottom of elements +// that are clearfixed. +// 2. The use of `table` rather than `block` is only necessary if using +// `:before` to contain the top-margins of child elements. +.clearfix() { + &:before, + &:after { + content: " "; /* 1 */ + display: table; /* 2 */ + } + &:after { + clear: both; + } +} + +// WebKit-style focus +.tab-focus() { + // Default + outline: thin dotted #333; + // WebKit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// Sizing shortcuts +.size(@width; @height) { + width: @width; + height: @height; +} +.square(@size) { + .size(@size; @size); +} + +// Placeholder text +.placeholder(@color: @input-color-placeholder) { + &:-moz-placeholder { color: @color; } // Firefox 4-18 + &::-moz-placeholder { color: @color; } // Firefox 19+ + &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+ + &::-webkit-input-placeholder { color: @color; } // Safari and Chrome +} + +// Text overflow +// Requires inline-block or block for proper styling +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// +// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for +// mixins being reused as classes with the same name, this doesn't hold up. As +// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. Note +// that we cannot chain the mixins together in Less, so they are repeated. +// +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 + +// Deprecated as of v3.0.1 (will be removed in v4) +.hide-text() { + font: ~"0/0" a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +// New mixin to use as of v3.0.1 +.text-hide() { + font: ~"0/0" a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Single side border-radius +.border-top-radius(@radius) { + border-top-right-radius: @radius; + border-top-left-radius: @radius; +} +.border-right-radius(@radius) { + border-bottom-right-radius: @radius; + border-top-right-radius: @radius; +} +.border-bottom-radius(@radius) { + border-bottom-right-radius: @radius; + border-bottom-left-radius: @radius; +} +.border-left-radius(@radius) { + border-bottom-left-radius: @radius; + border-top-left-radius: @radius; +} + +// Drop shadows +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1 + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + transition: @transition; +} +.transition-property(@transition-property) { + -webkit-transition-property: @transition-property; + transition-property: @transition-property; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} +.transition-transform(@transition) { + -webkit-transition: -webkit-transform @transition; + -moz-transition: -moz-transform @transition; + -o-transition: -o-transform @transition; + transition: transform @transition; +} + +// Transformations +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); // IE9+ + transform: rotate(@degrees); +} +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -ms-transform: scale(@ratio); // IE9+ + transform: scale(@ratio); +} +.translate(@x; @y) { + -webkit-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); // IE9+ + transform: translate(@x, @y); +} +.skew(@x; @y) { + -webkit-transform: skew(@x, @y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ + transform: skew(@x, @y); +} +.translate3d(@x; @y; @z) { + -webkit-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} + +.rotateX(@degrees) { + -webkit-transform: rotateX(@degrees); + -ms-transform: rotateX(@degrees); // IE9+ + transform: rotateX(@degrees); +} +.rotateY(@degrees) { + -webkit-transform: rotateY(@degrees); + -ms-transform: rotateY(@degrees); // IE9+ + transform: rotateY(@degrees); +} +.perspective(@perspective) { + -webkit-perspective: @perspective; + -moz-perspective: @perspective; + perspective: @perspective; +} +.perspective-origin(@perspective) { + -webkit-perspective-origin: @perspective; + -moz-perspective-origin: @perspective; + perspective-origin: @perspective; +} +.transform-origin(@origin) { + -webkit-transform-origin: @origin; + -moz-transform-origin: @origin; + transform-origin: @origin; +} + +// Animations +.animation(@animation) { + -webkit-animation: @animation; + animation: @animation; +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden` +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// User select +// For selecting text on the page +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; // IE10+ + -o-user-select: @select; + user-select: @select; +} + +// Resize anything +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +.content-columns(@column-count; @column-gap: @grid-gutter-width) { + -webkit-column-count: @column-count; + -moz-column-count: @column-count; + column-count: @column-count; + -webkit-column-gap: @column-gap; + -moz-column-gap: @column-gap; + column-gap: @column-gap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; // IE10+ + -o-hyphens: @mode; + hyphens: @mode; +} + +// Opacity +.opacity(@opacity) { + opacity: @opacity; + // IE8 filter + @opacity-ie: (@opacity * 100); + filter: ~"alpha(opacity=@{opacity-ie})"; +} + + + +// GRADIENTS +// -------------------------------------------------- + +#gradient { + + // Horizontal gradient, from left to right + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-gradient(linear, @start-percent top, @end-percent top, from(@start-color), to(@end-color)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, color-stop(@start-color @start-percent), color-stop(@end-color @end-percent)); // Safari 5.1+, Chrome 10+ + background-image: -moz-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // FF 3.6+ + background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + // Vertical gradient, from top to bottom + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-gradient(linear, left @start-percent, left @end-percent, from(@start-color), to(@end-color)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1+, Chrome 10+ + background-image: -moz-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // FF 3.6+ + background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + .directional(@start-color: #555; @end-color: #333; @deg: 45deg) { + background-repeat: repeat-x; + background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1+, Chrome 10+ + background-image: -moz-linear-gradient(@deg, @start-color, @end-color); // FF 3.6+ + background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10 + } + .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@start-color), color-stop(@color-stop, @mid-color), to(@end-color)); + background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: -moz-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@start-color), color-stop(@color-stop, @mid-color), to(@end-color)); + background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-image: -moz-linear-gradient(top, @start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@inner-color: #555; @outer-color: #333) { + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@inner-color), to(@outer-color)); + background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color); + background-image: -moz-radial-gradient(circle, @inner-color, @outer-color); + background-image: radial-gradient(circle, @inner-color, @outer-color); + background-repeat: no-repeat; + } + .striped(@color: rgba(255,255,255,.15); @angle: 45deg) { + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, @color), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, @color), color-stop(.75, @color), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + } +} + +// Reset filters for IE +// +// When you need to remove a gradient background, do not forget to use this to reset +// the IE filter for IE9 and below. +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + + + +// Retina images +// +// Short retina mixin for setting background-image and -size + +.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) { + background-image: url("@{file-1x}"); + + @media + only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and ( min--moz-device-pixel-ratio: 2), + only screen and ( -o-min-device-pixel-ratio: 2/1), + only screen and ( min-device-pixel-ratio: 2), + only screen and ( min-resolution: 192dpi), + only screen and ( min-resolution: 2dppx) { + background-image: url("@{file-2x}"); + background-size: @width-1x @height-1x; + } +} + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. + +.img-responsive(@display: block;) { + display: @display; + max-width: 100%; // Part 1: Set a maximum relative to the parent + height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching +} + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +.nav-divider(@color: #e5e5e5) { + height: 1px; + margin: ((@line-height-computed / 2) - 1) 0; + overflow: hidden; + background-color: @color; +} + +// Panels +// ------------------------- +.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border;) { + border-color: @border; + + & > .panel-heading { + color: @heading-text-color; + background-color: @heading-bg-color; + border-color: @heading-border; + + + .panel-collapse .panel-body { + border-top-color: @border; + } + & > .dropdown .caret { + border-color: @heading-text-color transparent; + } + } + & > .panel-footer { + + .panel-collapse .panel-body { + border-bottom-color: @border; + } + } +} + +// Alerts +// ------------------------- +.alert-variant(@background; @border; @text-color) { + background-color: @background; + border-color: @border; + color: @text-color; + + hr { + border-top-color: darken(@border, 5%); + } + .alert-link { + color: darken(@text-color, 10%); + } +} + +// Tables +// ------------------------- +.table-row-variant(@state; @background; @border) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table > thead > tr, + .table > tbody > tr, + .table > tfoot > tr { + > td.@{state}, + > th.@{state}, + &.@{state} > td, + &.@{state} > th { + background-color: @background; + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover > tbody > tr { + > td.@{state}:hover, + > th.@{state}:hover, + &.@{state}:hover > td, + &.@{state}:hover > th { + background-color: darken(@background, 5%); + } + } +} + +// Button variants +// ------------------------- +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons +.button-variant(@color; @background; @border) { + color: @color; + background-color: @background; + border-color: @border; + + &:hover, + &:focus, + &:active, + &.active, + .open .dropdown-toggle& { + color: @color; + background-color: darken(@background, 8%); + border-color: darken(@border, 12%); + } + &:active, + &.active, + .open .dropdown-toggle& { + background-image: none; + } + &.disabled, + &[disabled], + fieldset[disabled] & { + &, + &:hover, + &:focus, + &:active, + &.active { + background-color: @background; + border-color: @border; + } + } +} + +// Button sizes +// ------------------------- +.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + line-height: @line-height; + border-radius: @border-radius; +} + +// Pagination +// ------------------------- +.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) { + > li { + > a, + > span { + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + } + &:first-child { + > a, + > span { + .border-left-radius(@border-radius); + } + } + &:last-child { + > a, + > span { + .border-right-radius(@border-radius); + } + } + } +} + +// Labels +// ------------------------- +.label-variant(@color) { + background-color: @color; + &[href] { + &:hover, + &:focus { + background-color: darken(@color, 10%); + } + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. +.navbar-vertical-align(@element-height) { + margin-top: ((@navbar-height - @element-height) / 2); + margin-bottom: ((@navbar-height - @element-height) / 2); +} + +// Progress bars +// ------------------------- +.progress-bar-variant(@color) { + background-color: @color; + .progress-striped & { + #gradient > .striped(); + } +} + +// Responsive utilities +// ------------------------- +// More easily include all the states for responsive-utilities.less. +.responsive-visibility() { + display: block !important; + tr& { display: table-row !important; } + th&, + td& { display: table-cell !important; } +} + +.responsive-invisibility() { + &, + tr&, + th&, + td& { display: none !important; } +} + + +// Grid System +// ----------- + +// Centered container element +.container-fixed() { + margin-right: auto; + margin-left: auto; + padding-left: (@grid-gutter-width / 2); + padding-right: (@grid-gutter-width / 2); + .clearfix(); +} + +// Creates a wrapper for a series of columns +.make-row(@gutter: @grid-gutter-width) { + margin-left: (@gutter / -2); + margin-right: (@gutter / -2); + .clearfix(); +} + +// Generate the extra small columns +.make-xs-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + float: left; + width: percentage((@columns / @grid-columns)); + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); +} + +// Generate the small columns +.make-sm-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + // Calculate width based on number of columns available + @media (min-width: @screen-sm-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} + +// Generate the small column offsets +.make-sm-column-offset(@columns) { + @media (min-width: @screen-sm-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-push(@columns) { + @media (min-width: @screen-sm-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-pull(@columns) { + @media (min-width: @screen-sm-min) { + right: percentage((@columns / @grid-columns)); + } +} + +// Generate the medium columns +.make-md-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + // Calculate width based on number of columns available + @media (min-width: @screen-md-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} + +// Generate the medium column offsets +.make-md-column-offset(@columns) { + @media (min-width: @screen-md-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-push(@columns) { + @media (min-width: @screen-md) { + left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-pull(@columns) { + @media (min-width: @screen-md-min) { + right: percentage((@columns / @grid-columns)); + } +} + +// Generate the large columns +.make-lg-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + // Calculate width based on number of columns available + @media (min-width: @screen-lg-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} + +// Generate the large column offsets +.make-lg-column-offset(@columns) { + @media (min-width: @screen-lg-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-push(@columns) { + @media (min-width: @screen-lg-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-pull(@columns) { + @media (min-width: @screen-lg-min) { + right: percentage((@columns / @grid-columns)); + } +} + + +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `@grid-columns`. + +.make-grid-columns() { + // Common styles for all sizes of grid columns, widths 1-12 + .col(@index) when (@index = 1) { // initial + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col(@index + 1, @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col(@index + 1, ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: (@grid-gutter-width / 2); + padding-right: (@grid-gutter-width / 2); + } + } + .col(1); // kickstart it +} + +.make-grid-columns-float(@class) { + .col(@index) when (@index = 1) { // initial + @item: ~".col-@{class}-@{index}"; + .col(@index + 1, @item); + } + .col(@index, @list) when (@index < @grid-columns) { // general + @item: ~".col-@{class}-@{index}"; + .col(@index + 1, ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index = @grid-columns) { // terminal + @{list} { + float: left; + } + } + .col(1); // kickstart it +} + +.calc-grid(@index, @class, @type) when (@type = width) and (@index > 0) { + .col-@{class}-@{index} { + width: percentage((@index / @grid-columns)); + } +} +.calc-grid(@index, @class, @type) when (@type = push) { + .col-@{class}-push-@{index} { + left: percentage((@index / @grid-columns)); + } +} +.calc-grid(@index, @class, @type) when (@type = pull) { + .col-@{class}-pull-@{index} { + right: percentage((@index / @grid-columns)); + } +} +.calc-grid(@index, @class, @type) when (@type = offset) { + .col-@{class}-offset-@{index} { + margin-left: percentage((@index / @grid-columns)); + } +} + +// Basic looping in LESS +.make-grid(@index, @class, @type) when (@index >= 0) { + .calc-grid(@index, @class, @type); + // next iteration + .make-grid(@index - 1, @class, @type); +} + + +// Form validation states +// +// Used in forms.less to generate the form validation CSS for warnings, errors, +// and successes. + +.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) { + // Color the label and help text + .help-block, + .control-label, + .radio, + .checkbox, + .radio-inline, + .checkbox-inline { + color: @text-color; + } + // Set the border and box shadow on specific inputs to match + .form-control { + border-color: @border-color; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken(@border-color, 10%); + @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%); + .box-shadow(@shadow); + } + } + // Set validation states also for addons + .input-group-addon { + color: @text-color; + border-color: @border-color; + background-color: @background-color; + } +} + +// Form control focus state +// +// Generate a customized focus state and for any input with the specified color, +// which defaults to the `@input-focus-border` variable. +// +// We highly encourage you to not customize the default value, but instead use +// this to tweak colors on an as-needed basis. This aesthetic change is based on +// WebKit's default styles, but applicable to a wider range of browsers. Its +// usability and accessibility should be taken into account with any change. +// +// Example usage: change the default blue border and shadow to white for better +// contrast against a dark gray background. + +.form-control-focus(@color: @input-border-focus) { + @color-rgba: rgba(red(@color), green(@color), blue(@color), .6); + &:focus { + border-color: @color; + outline: 0; + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}"); + } +} + +// Form control sizing +// +// Relative text size, padding, and border-radii changes for form controls. For +// horizontal sizing, wrap controls in the predefined grid classes. ` + +
      +
      +

      + + {{item.actor.name}} + Expand replies: + {{item.links.replies[0].count}} +

      + {{item.object.content | html}} +
      + + {{reply.actor.name}}: {{reply.content | html}} +
      +
      +
      + + + + + */ +angular.module('ngResource', ['ng']). + factory('$resource', ['$http', '$parse', '$q', function($http, $parse, $q) { + var DEFAULT_ACTIONS = { + 'get': {method:'GET'}, + 'save': {method:'POST'}, + 'query': {method:'GET', isArray:true}, + 'remove': {method:'DELETE'}, + 'delete': {method:'DELETE'} + }; + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction, + getter = function(obj, path) { + return $parse(path)(obj); + }; + + /** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a + * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't + * have to be encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + function Route(template, defaults) { + this.template = template; + this.defaults = defaults || {}; + this.urlParams = {}; + } + + Route.prototype = { + setUrlParams: function(config, params, actionUrl) { + var self = this, + url = actionUrl || self.template, + val, + encodedVal; + + var urlParams = self.urlParams = {}; + forEach(url.split(/\W/), function(param){ + if (param === 'hasOwnProperty') { + throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); + } + if (!(new RegExp("^\\d+$").test(param)) && param && + (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { + urlParams[param] = true; + } + }); + url = url.replace(/\\:/g, ':'); + + params = params || {}; + forEach(self.urlParams, function(_, urlParam){ + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; + if (angular.isDefined(val) && val !== null) { + encodedVal = encodeUriSegment(val); + url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); + } else { + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, + leadingSlashes, tail) { + if (tail.charAt(0) == '/') { + return tail; + } else { + return leadingSlashes + tail; + } + }); + } + }); + + // strip trailing slashes and set the url + url = url.replace(/\/+$/, ''); + // then replace collapse `/.` if found in the last URL path segment before the query + // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` + url = url.replace(/\/\.(?=\w+($|\?))/, '.'); + // replace escaped `/\.` with `/.` + config.url = url.replace(/\/\\\./, '/.'); + + + // set params - delegate param encoding to $http + forEach(params, function(value, key){ + if (!self.urlParams[key]) { + config.params = config.params || {}; + config.params[key] = value; + } + }); + } + }; + + + function resourceFactory(url, paramDefaults, actions) { + var route = new Route(url); + + actions = extend({}, DEFAULT_ACTIONS, actions); + + function extractParams(data, actionParams){ + var ids = {}; + actionParams = extend({}, paramDefaults, actionParams); + forEach(actionParams, function(value, key){ + if (isFunction(value)) { value = value(); } + ids[key] = value && value.charAt && value.charAt(0) == '@' ? + getter(data, value.substr(1)) : value; + }); + return ids; + } + + function defaultResponseInterceptor(response) { + return response.resource; + } + + function Resource(value){ + copy(value || {}, this); + } + + forEach(actions, function(action, name) { + var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + + Resource[name] = function(a1, a2, a3, a4) { + var params = {}, data, success, error; + + /* jshint -W086 */ /* (purposefully fall through case statements) */ + switch(arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw $resourceMinErr('badargs', + "Expected up to 4 arguments [params, data, success, error], got {0} arguments", + arguments.length); + } + /* jshint +W086 */ /* (purposefully fall through case statements) */ + + var isInstanceCall = data instanceof Resource; + var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); + var httpConfig = {}; + var responseInterceptor = action.interceptor && action.interceptor.response || + defaultResponseInterceptor; + var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || + undefined; + + forEach(action, function(value, key) { + if (key != 'params' && key != 'isArray' && key != 'interceptor') { + httpConfig[key] = copy(value); + } + }); + + if (hasBody) httpConfig.data = data; + route.setUrlParams(httpConfig, + extend({}, extractParams(data, action.params || {}), params), + action.url); + + var promise = $http(httpConfig).then(function(response) { + var data = response.data, + promise = value.$promise; + + if (data) { + // Need to convert action.isArray to boolean in case it is undefined + // jshint -W018 + if ( angular.isArray(data) !== (!!action.isArray) ) { + throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + + 'response to contain an {0} but got an {1}', + action.isArray?'array':'object', angular.isArray(data)?'array':'object'); + } + // jshint +W018 + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + value.push(new Resource(item)); + }); + } else { + copy(data, value); + value.$promise = promise; + } + } + + value.$resolved = true; + + response.resource = value; + + return response; + }, function(response) { + value.$resolved = true; + + (error||noop)(response); + + return $q.reject(response); + }); + + promise = promise.then( + function(response) { + var value = responseInterceptor(response); + (success||noop)(value, response.headers); + return value; + }, + responseErrorInterceptor); + + if (!isInstanceCall) { + // we are creating instance / collection + // - set the initial promise + // - return the instance / collection + value.$promise = promise; + value.$resolved = false; + + return value; + } + + // instance call + return promise; + }; + + + Resource.prototype['$' + name] = function(params, success, error) { + if (isFunction(params)) { + error = success; success = params; params = {}; + } + var result = Resource[name](params, this, success, error); + return result.$promise || result; + }; + }); + + Resource.bind = function(additionalParamDefaults){ + return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + return Resource; + } + + return resourceFactory; + }]); + + +})(window, window.angular); diff --git a/client/lib/angular/angular-route.js b/client/lib/angular/angular-route.js new file mode 100755 index 0000000..b3b4ccf --- /dev/null +++ b/client/lib/angular/angular-route.js @@ -0,0 +1,880 @@ +/** + * @license AngularJS v1.2.0 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/** + * @ngdoc overview + * @name ngRoute + * @description + * + * # ngRoute + * + * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. + * + * {@installModule route} + * + *
      + */ + /* global -ngRouteModule */ +var ngRouteModule = angular.module('ngRoute', ['ng']). + provider('$route', $RouteProvider); + +/** + * @ngdoc object + * @name ngRoute.$routeProvider + * @function + * + * @description + * + * Used for configuring routes. See {@link ngRoute.$route $route} for an example. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + */ +function $RouteProvider(){ + function inherit(parent, extra) { + return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); + } + + var routes = {}; + + /** + * @ngdoc method + * @name ngRoute.$routeProvider#when + * @methodOf ngRoute.$routeProvider + * + * @param {string} path Route path (matched against `$location.path`). If `$location.path` + * contains redundant trailing slash or is missing one, the route will still match and the + * `$location.path` will be updated to add or drop the trailing slash to exactly match the + * route definition. + * + * * `path` can contain named groups starting with a colon (`:name`). All characters up + * to the next slash are matched and stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain named groups starting with a colon and ending with a star (`:name*`). + * All characters are eagerly stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain optional named groups with a question mark (`:name?`). + * + * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match + * `/color/brown/largecode/code/with/slashs/edit` and extract: + * + * * `color: brown` + * * `largecode: code/with/slashs`. + * + * + * @param {Object} route Mapping information to be assigned to `$route.current` on route + * match. + * + * Object properties: + * + * - `controller` – `{(string|function()=}` – Controller fn that should be associated with + * newly created scope or the name of a {@link angular.Module#controller registered + * controller} if passed as a string. + * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be + * published to scope under the `controllerAs` name. + * - `template` – `{string=|function()=}` – html template as a string or a function that + * returns an html template as a string which should be used by {@link + * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. + * This property takes precedence over `templateUrl`. + * + * If `template` is a function, it will be called with the following parameters: + * + * - `{Array.}` - route parameters extracted from the current + * `$location.path()` by applying the current route + * + * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html + * template that should be used by {@link ngRoute.directive:ngView ngView}. + * + * If `templateUrl` is a function, it will be called with the following parameters: + * + * - `{Array.}` - route parameters extracted from the current + * `$location.path()` by applying the current route + * + * - `resolve` - `{Object.=}` - An optional map of dependencies which should + * be injected into the controller. If any of these dependencies are promises, the router + * will wait for them all to be resolved or one to be rejected before the controller is + * instantiated. + * If all the promises are resolved successfully, the values of the resolved promises are + * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is + * fired. If any of the promises are rejected the + * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object + * is: + * + * - `key` – `{string}`: a name of a dependency to be injected into the controller. + * - `factory` - `{string|function}`: If `string` then it is an alias for a service. + * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} + * and the return value is treated as the dependency. If the result is a promise, it is + * resolved before its value is injected into the controller. Be aware that + * `ngRoute.$routeParams` will still refer to the previous route within these resolve + * functions. Use `$route.current.params` to access the new route parameters, instead. + * + * - `redirectTo` – {(string|function())=} – value to update + * {@link ng.$location $location} path with and trigger route redirection. + * + * If `redirectTo` is a function, it will be called with the following parameters: + * + * - `{Object.}` - route parameters extracted from the current + * `$location.path()` by applying the current route templateUrl. + * - `{string}` - current `$location.path()` + * - `{Object}` - current `$location.search()` + * + * The custom `redirectTo` function is expected to return a string which will be used + * to update `$location.path()` and `$location.search()`. + * + * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` + * or `$location.hash()` changes. + * + * If the option is set to `false` and url in the browser changes, then + * `$routeUpdate` event is broadcasted on the root scope. + * + * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive + * + * If the option is set to `true`, then the particular route can be matched without being + * case sensitive + * + * @returns {Object} self + * + * @description + * Adds a new route definition to the `$route` service. + */ + this.when = function(path, route) { + routes[path] = angular.extend( + {reloadOnSearch: true}, + route, + path && pathRegExp(path, route) + ); + + // create redirection for trailing slashes + if (path) { + var redirectPath = (path[path.length-1] == '/') + ? path.substr(0, path.length-1) + : path +'/'; + + routes[redirectPath] = angular.extend( + {redirectTo: path}, + pathRegExp(redirectPath, route) + ); + } + + return this; + }; + + /** + * @param path {string} path + * @param opts {Object} options + * @return {?Object} + * + * @description + * Normalizes the given path, returning a regular expression + * and the original path. + * + * Inspired by pathRexp in visionmedia/express/lib/utils.js. + */ + function pathRegExp(path, opts) { + var insensitive = opts.caseInsensitiveMatch, + ret = { + originalPath: path, + regexp: path + }, + keys = ret.keys = []; + + path = path + .replace(/([().])/g, '\\$1') + .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ + var optional = option === '?' ? option : null; + var star = option === '*' ? option : null; + keys.push({ name: key, optional: !!optional }); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (star && '(.+?)' || '([^/]+)') + + (optional || '') + + ')' + + (optional || ''); + }) + .replace(/([\/$\*])/g, '\\$1'); + + ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); + return ret; + } + + /** + * @ngdoc method + * @name ngRoute.$routeProvider#otherwise + * @methodOf ngRoute.$routeProvider + * + * @description + * Sets route definition that will be used on route change when no other route definition + * is matched. + * + * @param {Object} params Mapping information to be assigned to `$route.current`. + * @returns {Object} self + */ + this.otherwise = function(params) { + this.when(null, params); + return this; + }; + + + this.$get = ['$rootScope', + '$location', + '$routeParams', + '$q', + '$injector', + '$http', + '$templateCache', + '$sce', + function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { + + /** + * @ngdoc object + * @name ngRoute.$route + * @requires $location + * @requires $routeParams + * + * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * + * @property {Array.} routes Array of all configured routes. + * + * @description + * `$route` is used for deep-linking URLs to controllers and views (HTML partials). + * It watches `$location.url()` and tries to map the path to an existing route definition. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. + * + * The `$route` service is typically used in conjunction with the + * {@link ngRoute.directive:ngView `ngView`} directive and the + * {@link ngRoute.$routeParams `$routeParams`} service. + * + * @example + This example shows how changing the URL hash causes the `$route` to match a route against the + URL, and the `ngView` pulls in the partial. + + Note that this example is using {@link ng.directive:script inlined templates} + to get it working on jsfiddle as well. + + + +
      + Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
      + +
      +
      + +
      $location.path() = {{$location.path()}}
      +
      $route.current.templateUrl = {{$route.current.templateUrl}}
      +
      $route.current.params = {{$route.current.params}}
      +
      $route.current.scope.name = {{$route.current.scope.name}}
      +
      $routeParams = {{$routeParams}}
      +
      +
      + + + controller: {{name}}
      + Book Id: {{params.bookId}}
      +
      + + + controller: {{name}}
      + Book Id: {{params.bookId}}
      + Chapter Id: {{params.chapterId}} +
      + + + angular.module('ngViewExample', ['ngRoute']) + + .config(function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl, + resolve: { + // I will cause a 1 second delay + delay: function($q, $timeout) { + var delay = $q.defer(); + $timeout(delay.resolve, 1000); + return delay.promise; + } + } + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($scope, $route, $routeParams, $location) { + $scope.$route = $route; + $scope.$location = $location; + $scope.$routeParams = $routeParams; + } + + function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + sleep(2); // promises are not part of scenario waiting + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
      + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeStart + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occurs. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} next Future route information. + * @param {Route} current Current route information. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeSuccess + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted after a route dependencies are resolved. + * {@link ngRoute.directive:ngView ngView} listens for the directive + * to instantiate the controller and render the view. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} current Current route information. + * @param {Route|Undefined} previous Previous route information, or undefined if current is + * first route entered. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeError + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted if any of the resolve promises are rejected. + * + * @param {Object} angularEvent Synthetic event object + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeUpdate + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + */ + + var forceReload = false, + $route = { + routes: routes, + + /** + * @ngdoc method + * @name ngRoute.$route#reload + * @methodOf ngRoute.$route + * + * @description + * Causes `$route` service to reload the current route even if + * {@link ng.$location $location} hasn't changed. + * + * As a result of that, {@link ngRoute.directive:ngView ngView} + * creates new scope, reinstantiates the controller. + */ + reload: function() { + forceReload = true; + $rootScope.$evalAsync(updateRoute); + } + }; + + $rootScope.$on('$locationChangeSuccess', updateRoute); + + return $route; + + ///////////////////////////////////////////////////// + + /** + * @param on {string} current url + * @param route {Object} route regexp to match the url against + * @return {?Object} + * + * @description + * Check if the route matches the current url. + * + * Inspired by match in + * visionmedia/express/lib/router/router.js. + */ + function switchRouteMatcher(on, route) { + var keys = route.keys, + params = {}; + + if (!route.regexp) return null; + + var m = route.regexp.exec(on); + if (!m) return null; + + for (var i = 1, len = m.length; i < len; ++i) { + var key = keys[i - 1]; + + var val = 'string' == typeof m[i] + ? decodeURIComponent(m[i]) + : m[i]; + + if (key && val) { + params[key.name] = val; + } + } + return params; + } + + function updateRoute() { + var next = parseRoute(), + last = $route.current; + + if (next && last && next.$$route === last.$$route + && angular.equals(next.pathParams, last.pathParams) + && !next.reloadOnSearch && !forceReload) { + last.params = next.params; + angular.copy(last.params, $routeParams); + $rootScope.$broadcast('$routeUpdate', last); + } else if (next || last) { + forceReload = false; + $rootScope.$broadcast('$routeChangeStart', next, last); + $route.current = next; + if (next) { + if (next.redirectTo) { + if (angular.isString(next.redirectTo)) { + $location.path(interpolate(next.redirectTo, next.params)).search(next.params) + .replace(); + } else { + $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) + .replace(); + } + } + } + + $q.when(next). + then(function() { + if (next) { + var locals = angular.extend({}, next.resolve), + template, templateUrl; + + angular.forEach(locals, function(value, key) { + locals[key] = angular.isString(value) ? + $injector.get(value) : $injector.invoke(value); + }); + + if (angular.isDefined(template = next.template)) { + if (angular.isFunction(template)) { + template = template(next.params); + } + } else if (angular.isDefined(templateUrl = next.templateUrl)) { + if (angular.isFunction(templateUrl)) { + templateUrl = templateUrl(next.params); + } + templateUrl = $sce.getTrustedResourceUrl(templateUrl); + if (angular.isDefined(templateUrl)) { + next.loadedTemplateUrl = templateUrl; + template = $http.get(templateUrl, {cache: $templateCache}). + then(function(response) { return response.data; }); + } + } + if (angular.isDefined(template)) { + locals['$template'] = template; + } + return $q.all(locals); + } + }). + // after route change + then(function(locals) { + if (next == $route.current) { + if (next) { + next.locals = locals; + angular.copy(next.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', next, last); + } + }, function(error) { + if (next == $route.current) { + $rootScope.$broadcast('$routeChangeError', next, last, error); + } + }); + } + } + + + /** + * @returns the current active route, by matching it against the URL + */ + function parseRoute() { + // Match a route + var params, match; + angular.forEach(routes, function(route, path) { + if (!match && (params = switchRouteMatcher($location.path(), route))) { + match = inherit(route, { + params: angular.extend({}, $location.search(), params), + pathParams: params}); + match.$$route = route; + } + }); + // No route matched; fallback to "otherwise" route + return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); + } + + /** + * @returns interpolation of the redirect path with the parameters + */ + function interpolate(string, params) { + var result = []; + angular.forEach((string||'').split(':'), function(segment, i) { + if (i === 0) { + result.push(segment); + } else { + var segmentMatch = segment.match(/(\w+)(.*)/); + var key = segmentMatch[1]; + result.push(params[key]); + result.push(segmentMatch[2] || ''); + delete params[key]; + } + }); + return result.join(''); + } + }]; +} + +ngRouteModule.provider('$routeParams', $RouteParamsProvider); + + +/** + * @ngdoc object + * @name ngRoute.$routeParams + * @requires $route + * + * @description + * The `$routeParams` service allows you to retrieve the current set of route parameters. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * The route parameters are a combination of {@link ng.$location `$location`}'s + * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. + * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `search` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * Note that the `$routeParams` are only updated *after* a route change completes successfully. + * This means that you cannot rely on `$routeParams` being correct in route resolve functions. + * Instead you can use `$route.current.params` to access the new route's parameters. + * + * @example + *
      + *  // Given:
      + *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
      + *  // Route: /Chapter/:chapterId/Section/:sectionId
      + *  //
      + *  // Then
      + *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
      + * 
      + */ +function $RouteParamsProvider() { + this.$get = function() { return {}; }; +} + +ngRouteModule.directive('ngView', ngViewFactory); + +/** + * @ngdoc directive + * @name ngRoute.directive:ngView + * @restrict ECA + * + * @description + * # Overview + * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * @example + + +
      + Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
      + +
      +
      +
      +
      + +
      $location.path() = {{main.$location.path()}}
      +
      $route.current.templateUrl = {{main.$route.current.templateUrl}}
      +
      $route.current.params = {{main.$route.current.params}}
      +
      $route.current.scope.name = {{main.$route.current.scope.name}}
      +
      $routeParams = {{main.$routeParams}}
      +
      +
      + + +
      + controller: {{book.name}}
      + Book Id: {{book.params.bookId}}
      +
      +
      + + +
      + controller: {{chapter.name}}
      + Book Id: {{chapter.params.bookId}}
      + Chapter Id: {{chapter.params.chapterId}} +
      +
      + + + .view-animate-container { + position:relative; + height:100px!important; + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .view-animate { + padding:10px; + } + + .view-animate.ng-enter, .view-animate.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + + display:block; + width:100%; + border-left:1px solid black; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + padding:10px; + } + + .view-animate.ng-enter { + left:100%; + } + .view-animate.ng-enter.ng-enter-active { + left:0; + } + .view-animate.ng-leave.ng-leave-active { + left:-100%; + } + + + + angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], + function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl, + controllerAs: 'book' + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl, + controllerAs: 'chapter' + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($route, $routeParams, $location) { + this.$route = $route; + this.$location = $location; + this.$routeParams = $routeParams; + } + + function BookCntl($routeParams) { + this.name = "BookCntl"; + this.params = $routeParams; + } + + function ChapterCntl($routeParams) { + this.name = "ChapterCntl"; + this.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
      + */ + + +/** + * @ngdoc event + * @name ngRoute.directive:ngView#$viewContentLoaded + * @eventOf ngRoute.directive:ngView + * @eventType emit on the current ngView scope + * @description + * Emitted every time the ngView content is reloaded. + */ +ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate']; +function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) { + return { + restrict: 'ECA', + terminal: true, + priority: 400, + transclude: 'element', + compile: function(element, attr, linker) { + return function(scope, $element, attr) { + var currentScope, + currentElement, + autoScrollExp = attr.autoscroll, + onloadExp = attr.onload || ''; + + scope.$on('$routeChangeSuccess', update); + update(); + + function cleanupLastView() { + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if(currentElement) { + $animate.leave(currentElement); + currentElement = null; + } + } + + function update() { + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; + + if (template) { + var newScope = scope.$new(); + linker(newScope, function(clone) { + clone.html(template); + $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { + if (angular.isDefined(autoScrollExp) + && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }); + + cleanupLastView(); + + var link = $compile(clone.contents()), + current = $route.current; + + currentScope = current.scope = newScope; + currentElement = clone; + + if (current.controller) { + locals.$scope = currentScope; + var controller = $controller(current.controller, locals); + if (current.controllerAs) { + currentScope[current.controllerAs] = controller; + } + clone.data('$ngControllerController', controller); + clone.children().data('$ngControllerController', controller); + } + + link(currentScope); + currentScope.$emit('$viewContentLoaded'); + currentScope.$eval(onloadExp); + }); + } else { + cleanupLastView(); + } + } + }; + } + }; +} + + +})(window, window.angular); diff --git a/client/lib/angular/angular-sanitize.js b/client/lib/angular/angular-sanitize.js new file mode 100755 index 0000000..d34522a --- /dev/null +++ b/client/lib/angular/angular-sanitize.js @@ -0,0 +1,577 @@ +/** + * @license AngularJS v1.2.0 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +var $sanitizeMinErr = angular.$$minErr('$sanitize'); + +/** + * @ngdoc overview + * @name ngSanitize + * @description + * + * # ngSanitize + * + * The `ngSanitize` module provides functionality to sanitize HTML. + * + * {@installModule sanitize} + * + *
      + * + * See {@link ngSanitize.$sanitize `$sanitize`} for usage. + */ + +/* + * HTML Parser By Misko Hevery (misko@hevery.com) + * based on: HTML Parser By John Resig (ejohn.org) + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + * + * // Use like so: + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + */ + + +/** + * @ngdoc service + * @name ngSanitize.$sanitize + * @function + * + * @description + * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are + * then serialized back to properly escaped html string. This means that no unsafe input can make + * it into the returned string, however, since our parser is more strict than a typical browser + * parser, it's possible that some obscure input, which would be recognized as valid HTML by a + * browser, won't make it through the sanitizer. + * + * @param {string} html Html input. + * @returns {string} Sanitized html. + * + * @example + + + +
      + Snippet: + + + + + + + + + + + + + + + + + + + + + + + + + +
      DirectiveHowSourceRendered
      ng-bind-htmlAutomatically uses $sanitize
      <div ng-bind-html="snippet">
      </div>
      ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value +
      <div ng-bind-html="deliberatelyTrustDangerousSnippet()">
      +</div>
      +
      ng-bindAutomatically escapes
      <div ng-bind="snippet">
      </div>
      +
      +
      + + it('should sanitize the html snippet by default', function() { + expect(using('#bind-html-with-sanitize').element('div').html()). + toBe('

      an html\nclick here\nsnippet

      '); + }); + + it('should inline raw snippet if bound to a trusted value', function() { + expect(using('#bind-html-with-trust').element("div").html()). + toBe("

      an html\n" + + "click here\n" + + "snippet

      "); + }); + + it('should escape snippet without any filter', function() { + expect(using('#bind-default').element('div').html()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + + it('should update', function() { + input('snippet').enter('new text'); + expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new text'); + expect(using('#bind-html-with-trust').element('div').html()).toBe( + 'new text'); + expect(using('#bind-default').element('div').html()).toBe( + "new <b onclick=\"alert(1)\">text</b>"); + }); +
      +
      + */ +var $sanitize = function(html) { + var buf = []; + htmlParser(html, htmlSanitizeWriter(buf)); + return buf.join(''); +}; + + +// Regular Expressions for parsing tags and attributes +var START_TAG_REGEXP = + /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, + END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, + ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, + BEGIN_TAG_REGEXP = /^/g, + DOCTYPE_REGEXP = /]*?)>/i, + CDATA_REGEXP = //g, + URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/i, + // Match everything outside of normal chars and " (quote character) + NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + + +// Good source of info about elements and attributes +// http://dev.w3.org/html5/spec/Overview.html#semantics +// http://simon.html5.org/html-elements + +// Safe Void Elements - HTML5 +// http://dev.w3.org/html5/spec/Overview.html#void-elements +var voidElements = makeMap("area,br,col,hr,img,wbr"); + +// Elements that you can, intentionally, leave open (and which close themselves) +// http://dev.w3.org/html5/spec/Overview.html#optional-tags +var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), + optionalEndTagInlineElements = makeMap("rp,rt"), + optionalEndTagElements = angular.extend({}, + optionalEndTagInlineElements, + optionalEndTagBlockElements); + +// Safe Block Elements - HTML5 +var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + + "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + + "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); + +// Inline Elements - HTML5 +var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + + "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + + "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); + + +// Special Elements (can contain anything) +var specialElements = makeMap("script,style"); + +var validElements = angular.extend({}, + voidElements, + blockElements, + inlineElements, + optionalEndTagElements); + +//Attributes that have href and hence need to be sanitized +var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); +var validAttrs = angular.extend({}, uriAttrs, makeMap( + 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ + 'scope,scrolling,shape,span,start,summary,target,title,type,'+ + 'valign,value,vspace,width')); + +function makeMap(str) { + var obj = {}, items = str.split(','), i; + for (i = 0; i < items.length; i++) obj[items[i]] = true; + return obj; +} + + +/** + * @example + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + * @param {string} html string + * @param {object} handler + */ +function htmlParser( html, handler ) { + var index, chars, match, stack = [], last = html; + stack.last = function() { return stack[ stack.length - 1 ]; }; + + while ( html ) { + chars = true; + + // Make sure we're not in a script or style element + if ( !stack.last() || !specialElements[ stack.last() ] ) { + + // Comment + if ( html.indexOf("", index) === index) { + if (handler.comment) handler.comment( html.substring( 4, index ) ); + html = html.substring( index + 3 ); + chars = false; + } + // DOCTYPE + } else if ( DOCTYPE_REGEXP.test(html) ) { + match = html.match( DOCTYPE_REGEXP ); + + if ( match ) { + html = html.replace( match[0] , ''); + chars = false; + } + // end tag + } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { + match = html.match( END_TAG_REGEXP ); + + if ( match ) { + html = html.substring( match[0].length ); + match[0].replace( END_TAG_REGEXP, parseEndTag ); + chars = false; + } + + // start tag + } else if ( BEGIN_TAG_REGEXP.test(html) ) { + match = html.match( START_TAG_REGEXP ); + + if ( match ) { + html = html.substring( match[0].length ); + match[0].replace( START_TAG_REGEXP, parseStartTag ); + chars = false; + } + } + + if ( chars ) { + index = html.indexOf("<"); + + var text = index < 0 ? html : html.substring( 0, index ); + html = index < 0 ? "" : html.substring( index ); + + if (handler.chars) handler.chars( decodeEntities(text) ); + } + + } else { + html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), + function(all, text){ + text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); + + if (handler.chars) handler.chars( decodeEntities(text) ); + + return ""; + }); + + parseEndTag( "", stack.last() ); + } + + if ( html == last ) { + throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + + "of html: {0}", html); + } + last = html; + } + + // Clean up any remaining tags + parseEndTag(); + + function parseStartTag( tag, tagName, rest, unary ) { + tagName = angular.lowercase(tagName); + if ( blockElements[ tagName ] ) { + while ( stack.last() && inlineElements[ stack.last() ] ) { + parseEndTag( "", stack.last() ); + } + } + + if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { + parseEndTag( "", tagName ); + } + + unary = voidElements[ tagName ] || !!unary; + + if ( !unary ) + stack.push( tagName ); + + var attrs = {}; + + rest.replace(ATTR_REGEXP, + function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { + var value = doubleQuotedValue + || singleQuotedValue + || unquotedValue + || ''; + + attrs[name] = decodeEntities(value); + }); + if (handler.start) handler.start( tagName, attrs, unary ); + } + + function parseEndTag( tag, tagName ) { + var pos = 0, i; + tagName = angular.lowercase(tagName); + if ( tagName ) + // Find the closest opened tag of the same type + for ( pos = stack.length - 1; pos >= 0; pos-- ) + if ( stack[ pos ] == tagName ) + break; + + if ( pos >= 0 ) { + // Close all the open elements, up the stack + for ( i = stack.length - 1; i >= pos; i-- ) + if (handler.end) handler.end( stack[ i ] ); + + // Remove the open elements from the stack + stack.length = pos; + } + } +} + +/** + * decodes all entities into regular string + * @param value + * @returns {string} A string with decoded entities. + */ +var hiddenPre=document.createElement("pre"); +function decodeEntities(value) { + hiddenPre.innerHTML=value.replace(//g, '>'); +} + +/** + * create an HTML/XML writer which writes to buffer + * @param {Array} buf use buf.jain('') to get out sanitized html string + * @returns {object} in the form of { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * } + */ +function htmlSanitizeWriter(buf){ + var ignore = false; + var out = angular.bind(buf, buf.push); + return { + start: function(tag, attrs, unary){ + tag = angular.lowercase(tag); + if (!ignore && specialElements[tag]) { + ignore = tag; + } + if (!ignore && validElements[tag] === true) { + out('<'); + out(tag); + angular.forEach(attrs, function(value, key){ + var lkey=angular.lowercase(key); + if (validAttrs[lkey]===true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { + out(' '); + out(key); + out('="'); + out(encodeEntities(value)); + out('"'); + } + }); + out(unary ? '/>' : '>'); + } + }, + end: function(tag){ + tag = angular.lowercase(tag); + if (!ignore && validElements[tag] === true) { + out(''); + } + if (tag == ignore) { + ignore = false; + } + }, + chars: function(chars){ + if (!ignore) { + out(encodeEntities(chars)); + } + } + }; +} + + +// define ngSanitize module and register $sanitize service +angular.module('ngSanitize', []).value('$sanitize', $sanitize); + +/* global htmlSanitizeWriter: false */ + +/** + * @ngdoc filter + * @name ngSanitize.filter:linky + * @function + * + * @description + * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and + * plain email address links. + * + * Requires the {@link ngSanitize `ngSanitize`} module to be installed. + * + * @param {string} text Input text. + * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. + * @returns {string} Html-linkified text. + * + * @usage + + * + * @example + + + +
      + Snippet: + + + + + + + + + + + + + + + + + + + + + +
      FilterSourceRendered
      linky filter +
      <div ng-bind-html="snippet | linky">
      </div>
      +
      +
      +
      linky target +
      <div ng-bind-html="snippetWithTarget | linky:'_blank'">
      </div>
      +
      +
      +
      no filter
      <div ng-bind="snippet">
      </div>
      + + + it('should linkify the snippet with urls', function() { + expect(using('#linky-filter').binding('snippet | linky')). + toBe('Pretty text with some links: ' + + 'http://angularjs.org/, ' + + 'us@somewhere.org, ' + + 'another@somewhere.org, ' + + 'and one more: ftp://127.0.0.1/.'); + }); + + it ('should not linkify snippet without the linky filter', function() { + expect(using('#escaped-html').binding('snippet')). + toBe("Pretty text with some links:\n" + + "http://angularjs.org/,\n" + + "mailto:us@somewhere.org,\n" + + "another@somewhere.org,\n" + + "and one more: ftp://127.0.0.1/."); + }); + + it('should update', function() { + input('snippet').enter('new http://link.'); + expect(using('#linky-filter').binding('snippet | linky')). + toBe('new http://link.'); + expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); + }); + + it('should work with the target property', function() { + expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")). + toBe('http://angularjs.org/'); + }); + + + */ +angular.module('ngSanitize').filter('linky', function() { + var LINKY_URL_REGEXP = + /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/, + MAILTO_REGEXP = /^mailto:/; + + return function(text, target) { + if (!text) return text; + var match; + var raw = text; + var html = []; + // TODO(vojta): use $sanitize instead + var writer = htmlSanitizeWriter(html); + var url; + var i; + var properties = {}; + if (angular.isDefined(target)) { + properties.target = target; + } + while ((match = raw.match(LINKY_URL_REGEXP))) { + // We can not end in these as they are sometimes found at the end of the sentence + url = match[0]; + // if we did not match ftp/http/mailto then assume mailto + if (match[2] == match[3]) url = 'mailto:' + url; + i = match.index; + writer.chars(raw.substr(0, i)); + properties.href = url; + writer.start('a', properties); + writer.chars(match[0].replace(MAILTO_REGEXP, '')); + writer.end('a'); + raw = raw.substring(i + match[0].length); + } + writer.chars(raw); + return html.join(''); + }; +}); + + +})(window, window.angular); diff --git a/client/lib/angular/angular-touch.js b/client/lib/angular/angular-touch.js new file mode 100755 index 0000000..195f499 --- /dev/null +++ b/client/lib/angular/angular-touch.js @@ -0,0 +1,563 @@ +/** + * @license AngularJS v1.2.0 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/** + * @ngdoc overview + * @name ngTouch + * @description + * + * # ngTouch + * + * The `ngTouch` module provides touch events and other helpers for touch-enabled devices. + * The implementation is based on jQuery Mobile touch event handling + * ([jquerymobile.com](http://jquerymobile.com/)). + * + * {@installModule touch} + * + * See {@link ngTouch.$swipe `$swipe`} for usage. + * + *
      + * + */ + +// define ngTouch module +/* global -ngTouch */ +var ngTouch = angular.module('ngTouch', []); + +/* global ngTouch: false */ + + /** + * @ngdoc object + * @name ngTouch.$swipe + * + * @description + * The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe + * behavior, to make implementing swipe-related directives more convenient. + * + * Requires the {@link ngTouch `ngTouch`} module to be installed. + * + * `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by + * `ngCarousel` in a separate component. + * + * # Usage + * The `$swipe` service is an object with a single method: `bind`. `bind` takes an element + * which is to be watched for swipes, and an object with four handler functions. See the + * documentation for `bind` below. + */ + +ngTouch.factory('$swipe', [function() { + // The total distance in any direction before we make the call on swipe vs. scroll. + var MOVE_BUFFER_RADIUS = 10; + + function getCoordinates(event) { + var touches = event.touches && event.touches.length ? event.touches : [event]; + var e = (event.changedTouches && event.changedTouches[0]) || + (event.originalEvent && event.originalEvent.changedTouches && + event.originalEvent.changedTouches[0]) || + touches[0].originalEvent || touches[0]; + + return { + x: e.clientX, + y: e.clientY + }; + } + + return { + /** + * @ngdoc method + * @name ngTouch.$swipe#bind + * @methodOf ngTouch.$swipe + * + * @description + * The main method of `$swipe`. It takes an element to be watched for swipe motions, and an + * object containing event handlers. + * + * The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end` + * receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`. + * + * `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is + * watching for `touchmove` or `mousemove` events. These events are ignored until the total + * distance moved in either dimension exceeds a small threshold. + * + * Once this threshold is exceeded, either the horizontal or vertical delta is greater. + * - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow. + * - If the vertical distance is greater, this is a scroll, and we let the browser take over. + * A `cancel` event is sent. + * + * `move` is called on `mousemove` and `touchmove` after the above logic has determined that + * a swipe is in progress. + * + * `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`. + * + * `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling + * as described above. + * + */ + bind: function(element, eventHandlers) { + // Absolute total movement, used to control swipe vs. scroll. + var totalX, totalY; + // Coordinates of the start position. + var startCoords; + // Last event's position. + var lastPos; + // Whether a swipe is active. + var active = false; + + element.on('touchstart mousedown', function(event) { + startCoords = getCoordinates(event); + active = true; + totalX = 0; + totalY = 0; + lastPos = startCoords; + eventHandlers['start'] && eventHandlers['start'](startCoords, event); + }); + + element.on('touchcancel', function(event) { + active = false; + eventHandlers['cancel'] && eventHandlers['cancel'](event); + }); + + element.on('touchmove mousemove', function(event) { + if (!active) return; + + // Android will send a touchcancel if it thinks we're starting to scroll. + // So when the total distance (+ or - or both) exceeds 10px in either direction, + // we either: + // - On totalX > totalY, we send preventDefault() and treat this as a swipe. + // - On totalY > totalX, we let the browser handle it as a scroll. + + if (!startCoords) return; + var coords = getCoordinates(event); + + totalX += Math.abs(coords.x - lastPos.x); + totalY += Math.abs(coords.y - lastPos.y); + + lastPos = coords; + + if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) { + return; + } + + // One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll. + if (totalY > totalX) { + // Allow native scrolling to take over. + active = false; + eventHandlers['cancel'] && eventHandlers['cancel'](event); + return; + } else { + // Prevent the browser from scrolling. + event.preventDefault(); + eventHandlers['move'] && eventHandlers['move'](coords, event); + } + }); + + element.on('touchend mouseup', function(event) { + if (!active) return; + active = false; + eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event); + }); + } + }; +}]); + +/* global ngTouch: false */ + +/** + * @ngdoc directive + * @name ngTouch.directive:ngClick + * + * @description + * A more powerful replacement for the default ngClick designed to be used on touchscreen + * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending + * the click event. This version handles them immediately, and then prevents the + * following click event from propagating. + * + * Requires the {@link ngTouch `ngTouch`} module to be installed. + * + * This directive can fall back to using an ordinary click event, and so works on desktop + * browsers as well as mobile. + * + * This directive also sets the CSS class `ng-click-active` while the element is being held + * down (by a mouse click or touch) so you can restyle the depressed element if you wish. + * + * @element ANY + * @param {expression} ngClick {@link guide/expression Expression} to evaluate + * upon tap. (Event object is available as `$event`) + * + * @example + + + + count: {{ count }} + + + */ + +ngTouch.config(['$provide', function($provide) { + $provide.decorator('ngClickDirective', ['$delegate', function($delegate) { + // drop the default ngClick directive + $delegate.shift(); + return $delegate; + }]); +}]); + +ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', + function($parse, $timeout, $rootElement) { + var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag. + var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers. + var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click + var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks. + + var ACTIVE_CLASS_NAME = 'ng-click-active'; + var lastPreventedTime; + var touchCoordinates; + + + // TAP EVENTS AND GHOST CLICKS + // + // Why tap events? + // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're + // double-tapping, and then fire a click event. + // + // This delay sucks and makes mobile apps feel unresponsive. + // So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when + // the user has tapped on something. + // + // What happens when the browser then generates a click event? + // The browser, of course, also detects the tap and fires a click after a delay. This results in + // tapping/clicking twice. So we do "clickbusting" to prevent it. + // + // How does it work? + // We attach global touchstart and click handlers, that run during the capture (early) phase. + // So the sequence for a tap is: + // - global touchstart: Sets an "allowable region" at the point touched. + // - element's touchstart: Starts a touch + // (- touchmove or touchcancel ends the touch, no click follows) + // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold + // too long) and fires the user's tap handler. The touchend also calls preventGhostClick(). + // - preventGhostClick() removes the allowable region the global touchstart created. + // - The browser generates a click event. + // - The global click handler catches the click, and checks whether it was in an allowable region. + // - If preventGhostClick was called, the region will have been removed, the click is busted. + // - If the region is still there, the click proceeds normally. Therefore clicks on links and + // other elements without ngTap on them work normally. + // + // This is an ugly, terrible hack! + // Yeah, tell me about it. The alternatives are using the slow click events, or making our users + // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular + // encapsulates this ugly logic away from the user. + // + // Why not just put click handlers on the element? + // We do that too, just to be sure. The problem is that the tap event might have caused the DOM + // to change, so that the click fires in the same position but something else is there now. So + // the handlers are global and care only about coordinates and not elements. + + // Checks if the coordinates are close enough to be within the region. + function hit(x1, y1, x2, y2) { + return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD; + } + + // Checks a list of allowable regions against a click location. + // Returns true if the click should be allowed. + // Splices out the allowable region from the list after it has been used. + function checkAllowableRegions(touchCoordinates, x, y) { + for (var i = 0; i < touchCoordinates.length; i += 2) { + if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) { + touchCoordinates.splice(i, i + 2); + return true; // allowable region + } + } + return false; // No allowable region; bust it. + } + + // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick + // was called recently. + function onClick(event) { + if (Date.now() - lastPreventedTime > PREVENT_DURATION) { + return; // Too old. + } + + var touches = event.touches && event.touches.length ? event.touches : [event]; + var x = touches[0].clientX; + var y = touches[0].clientY; + // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label + // and on the input element). Depending on the exact browser, this second click we don't want + // to bust has either (0,0) or negative coordinates. + if (x < 1 && y < 1) { + return; // offscreen + } + + // Look for an allowable region containing this click. + // If we find one, that means it was created by touchstart and not removed by + // preventGhostClick, so we don't bust it. + if (checkAllowableRegions(touchCoordinates, x, y)) { + return; + } + + // If we didn't find an allowable region, bust the click. + event.stopPropagation(); + event.preventDefault(); + + // Blur focused form elements + event.target && event.target.blur(); + } + + + // Global touchstart handler that creates an allowable region for a click event. + // This allowable region can be removed by preventGhostClick if we want to bust it. + function onTouchStart(event) { + var touches = event.touches && event.touches.length ? event.touches : [event]; + var x = touches[0].clientX; + var y = touches[0].clientY; + touchCoordinates.push(x, y); + + $timeout(function() { + // Remove the allowable region. + for (var i = 0; i < touchCoordinates.length; i += 2) { + if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) { + touchCoordinates.splice(i, i + 2); + return; + } + } + }, PREVENT_DURATION, false); + } + + // On the first call, attaches some event handlers. Then whenever it gets called, it creates a + // zone around the touchstart where clicks will get busted. + function preventGhostClick(x, y) { + if (!touchCoordinates) { + $rootElement[0].addEventListener('click', onClick, true); + $rootElement[0].addEventListener('touchstart', onTouchStart, true); + touchCoordinates = []; + } + + lastPreventedTime = Date.now(); + + checkAllowableRegions(touchCoordinates, x, y); + } + + // Actual linking function. + return function(scope, element, attr) { + var clickHandler = $parse(attr.ngClick), + tapping = false, + tapElement, // Used to blur the element after a tap. + startTime, // Used to check if the tap was held too long. + touchStartX, + touchStartY; + + function resetState() { + tapping = false; + element.removeClass(ACTIVE_CLASS_NAME); + } + + element.on('touchstart', function(event) { + tapping = true; + tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement. + // Hack for Safari, which can target text nodes instead of containers. + if(tapElement.nodeType == 3) { + tapElement = tapElement.parentNode; + } + + element.addClass(ACTIVE_CLASS_NAME); + + startTime = Date.now(); + + var touches = event.touches && event.touches.length ? event.touches : [event]; + var e = touches[0].originalEvent || touches[0]; + touchStartX = e.clientX; + touchStartY = e.clientY; + }); + + element.on('touchmove', function(event) { + resetState(); + }); + + element.on('touchcancel', function(event) { + resetState(); + }); + + element.on('touchend', function(event) { + var diff = Date.now() - startTime; + + var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches : + ((event.touches && event.touches.length) ? event.touches : [event]); + var e = touches[0].originalEvent || touches[0]; + var x = e.clientX; + var y = e.clientY; + var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) ); + + if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) { + // Call preventGhostClick so the clickbuster will catch the corresponding click. + preventGhostClick(x, y); + + // Blur the focused element (the button, probably) before firing the callback. + // This doesn't work perfectly on Android Chrome, but seems to work elsewhere. + // I couldn't get anything to work reliably on Android Chrome. + if (tapElement) { + tapElement.blur(); + } + + if (!angular.isDefined(attr.disabled) || attr.disabled === false) { + element.triggerHandler('click', [event]); + } + } + + resetState(); + }); + + // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click + // something else nearby. + element.onclick = function(event) { }; + + // Actual click handler. + // There are three different kinds of clicks, only two of which reach this point. + // - On desktop browsers without touch events, their clicks will always come here. + // - On mobile browsers, the simulated "fast" click will call this. + // - But the browser's follow-up slow click will be "busted" before it reaches this handler. + // Therefore it's safe to use this directive on both mobile and desktop. + element.on('click', function(event, touchend) { + scope.$apply(function() { + clickHandler(scope, {$event: (touchend || event)}); + }); + }); + + element.on('mousedown', function(event) { + element.addClass(ACTIVE_CLASS_NAME); + }); + + element.on('mousemove mouseup', function(event) { + element.removeClass(ACTIVE_CLASS_NAME); + }); + + }; +}]); + +/* global ngTouch: false */ + +/** + * @ngdoc directive + * @name ngTouch.directive:ngSwipeLeft + * + * @description + * Specify custom behavior when an element is swiped to the left on a touchscreen device. + * A leftward swipe is a quick, right-to-left slide of the finger. + * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag + * too. + * + * Requires the {@link ngTouch `ngTouch`} module to be installed. + * + * @element ANY + * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate + * upon left swipe. (Event object is available as `$event`) + * + * @example + + +
      + Some list content, like an email in the inbox +
      +
      + + +
      +
      +
      + */ + +/** + * @ngdoc directive + * @name ngTouch.directive:ngSwipeRight + * + * @description + * Specify custom behavior when an element is swiped to the right on a touchscreen device. + * A rightward swipe is a quick, left-to-right slide of the finger. + * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag + * too. + * + * Requires the {@link ngTouch `ngTouch`} module to be installed. + * + * @element ANY + * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate + * upon right swipe. (Event object is available as `$event`) + * + * @example + + +
      + Some list content, like an email in the inbox +
      +
      + + +
      +
      +
      + */ + +function makeSwipeDirective(directiveName, direction, eventName) { + ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) { + // The maximum vertical delta for a swipe should be less than 75px. + var MAX_VERTICAL_DISTANCE = 75; + // Vertical distance should not be more than a fraction of the horizontal distance. + var MAX_VERTICAL_RATIO = 0.3; + // At least a 30px lateral motion is necessary for a swipe. + var MIN_HORIZONTAL_DISTANCE = 30; + + return function(scope, element, attr) { + var swipeHandler = $parse(attr[directiveName]); + + var startCoords, valid; + + function validSwipe(coords) { + // Check that it's within the coordinates. + // Absolute vertical distance must be within tolerances. + // Horizontal distance, we take the current X - the starting X. + // This is negative for leftward swipes and positive for rightward swipes. + // After multiplying by the direction (-1 for left, +1 for right), legal swipes + // (ie. same direction as the directive wants) will have a positive delta and + // illegal ones a negative delta. + // Therefore this delta must be positive, and larger than the minimum. + if (!startCoords) return false; + var deltaY = Math.abs(coords.y - startCoords.y); + var deltaX = (coords.x - startCoords.x) * direction; + return valid && // Short circuit for already-invalidated swipes. + deltaY < MAX_VERTICAL_DISTANCE && + deltaX > 0 && + deltaX > MIN_HORIZONTAL_DISTANCE && + deltaY / deltaX < MAX_VERTICAL_RATIO; + } + + $swipe.bind(element, { + 'start': function(coords, event) { + startCoords = coords; + valid = true; + }, + 'cancel': function(event) { + valid = false; + }, + 'end': function(coords, event) { + if (validSwipe(coords)) { + scope.$apply(function() { + element.triggerHandler(eventName); + swipeHandler(scope, {$event: event}); + }); + } + } + }); + }; + }]); +} + +// Left is negative X-coordinate, right is positive. +makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft'); +makeSwipeDirective('ngSwipeRight', 1, 'swiperight'); + + + +})(window, window.angular); diff --git a/client/lib/angular/angular.js b/client/lib/angular/angular.js new file mode 100755 index 0000000..fc5ab04 --- /dev/null +++ b/client/lib/angular/angular.js @@ -0,0 +1,20031 @@ +/** + * @license AngularJS v1.2.0 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, document, undefined) {'use strict'; + +/** + * @description + * + * This object provides a utility for producing rich Error messages within + * Angular. It can be called as follows: + * + * var exampleMinErr = minErr('example'); + * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); + * + * The above creates an instance of minErr in the example namespace. The + * resulting error will have a namespaced error code of example.one. The + * resulting error will replace {0} with the value of foo, and {1} with the + * value of bar. The object is not restricted in the number of arguments it can + * take. + * + * If fewer arguments are specified than necessary for interpolation, the extra + * interpolation markers will be preserved in the final string. + * + * Since data will be parsed statically during a build step, some restrictions + * are applied with respect to how minErr instances are created and called. + * Instances should have names of the form namespaceMinErr for a minErr created + * using minErr('namespace') . Error codes, namespaces and template strings + * should all be static strings, not variables or general expressions. + * + * @param {string} module The namespace to use for the new minErr instance. + * @returns {function(string, string, ...): Error} instance + */ + +function minErr(module) { + return function () { + var code = arguments[0], + prefix = '[' + (module ? module + ':' : '') + code + '] ', + template = arguments[1], + templateArgs = arguments, + stringify = function (obj) { + if (isFunction(obj)) { + return obj.toString().replace(/ \{[\s\S]*$/, ''); + } else if (isUndefined(obj)) { + return 'undefined'; + } else if (!isString(obj)) { + return JSON.stringify(obj); + } + return obj; + }, + message, i; + + message = prefix + template.replace(/\{\d+\}/g, function (match) { + var index = +match.slice(1, -1), arg; + + if (index + 2 < templateArgs.length) { + arg = templateArgs[index + 2]; + if (isFunction(arg)) { + return arg.toString().replace(/ ?\{[\s\S]*$/, ''); + } else if (isUndefined(arg)) { + return 'undefined'; + } else if (!isString(arg)) { + return toJson(arg); + } + return arg; + } + return match; + }); + + message = message + '\nhttp://errors.angularjs.org/' + version.full + '/' + + (module ? module + '/' : '') + code; + for (i = 2; i < arguments.length; i++) { + message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + + encodeURIComponent(stringify(arguments[i])); + } + + return new Error(message); + }; +} + +/* We need to tell jshint what variables are being exported */ +/* global + -angular, + -msie, + -jqLite, + -jQuery, + -slice, + -push, + -toString, + -ngMinErr, + -_angular, + -angularModule, + -nodeName_, + -uid, + + -lowercase, + -uppercase, + -manualLowercase, + -manualUppercase, + -nodeName_, + -isArrayLike, + -forEach, + -sortedKeys, + -forEachSorted, + -reverseParams, + -nextUid, + -setHashKey, + -extend, + -int, + -inherit, + -noop, + -identity, + -valueFn, + -isUndefined, + -isDefined, + -isObject, + -isString, + -isNumber, + -isDate, + -isArray, + -isFunction, + -isRegExp, + -isWindow, + -isScope, + -isFile, + -isBoolean, + -trim, + -isElement, + -makeMap, + -map, + -size, + -includes, + -indexOf, + -arrayRemove, + -isLeafNode, + -copy, + -shallowCopy, + -equals, + -csp, + -concat, + -sliceArgs, + -bind, + -toJsonReplacer, + -toJson, + -fromJson, + -toBoolean, + -startingTag, + -tryDecodeURIComponent, + -parseKeyValue, + -toKeyValue, + -encodeUriSegment, + -encodeUriQuery, + -angularInit, + -bootstrap, + -snake_case, + -bindJQuery, + -assertArg, + -assertArgFn, + -assertNotHasOwnProperty, + -getter, + -getBlockElements + +*/ + +//////////////////////////////////// + +/** + * @ngdoc function + * @name angular.lowercase + * @function + * + * @description Converts the specified string to lowercase. + * @param {string} string String to be converted to lowercase. + * @returns {string} Lowercased string. + */ +var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; + + +/** + * @ngdoc function + * @name angular.uppercase + * @function + * + * @description Converts the specified string to uppercase. + * @param {string} string String to be converted to uppercase. + * @returns {string} Uppercased string. + */ +var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; + + +var manualLowercase = function(s) { + /* jshint bitwise: false */ + return isString(s) + ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) + : s; +}; +var manualUppercase = function(s) { + /* jshint bitwise: false */ + return isString(s) + ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) + : s; +}; + + +// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish +// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods +// with correct but slower alternatives. +if ('i' !== 'I'.toLowerCase()) { + lowercase = manualLowercase; + uppercase = manualUppercase; +} + + +var /** holds major version number for IE or NaN for real browsers */ + msie, + jqLite, // delay binding since jQuery could be loaded after us. + jQuery, // delay binding + slice = [].slice, + push = [].push, + toString = Object.prototype.toString, + ngMinErr = minErr('ng'), + + + _angular = window.angular, + /** @name angular */ + angular = window.angular || (window.angular = {}), + angularModule, + nodeName_, + uid = ['0', '0', '0']; + +/** + * IE 11 changed the format of the UserAgent string. + * See http://msdn.microsoft.com/en-us/library/ms537503.aspx + */ +msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); +if (isNaN(msie)) { + msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); +} + + +/** + * @private + * @param {*} obj + * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, + * String ...) + */ +function isArrayLike(obj) { + if (obj == null || isWindow(obj)) { + return false; + } + + var length = obj.length; + + if (obj.nodeType === 1 && length) { + return true; + } + + return isString(obj) || isArray(obj) || length === 0 || + typeof length === 'number' && length > 0 && (length - 1) in obj; +} + +/** + * @ngdoc function + * @name angular.forEach + * @function + * + * @description + * Invokes the `iterator` function once for each item in `obj` collection, which can be either an + * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` + * is the value of an object property or an array element and `key` is the object property key or + * array element index. Specifying a `context` for the function is optional. + * + * Note: this function was previously known as `angular.foreach`. + * +
      +     var values = {name: 'misko', gender: 'male'};
      +     var log = [];
      +     angular.forEach(values, function(value, key){
      +       this.push(key + ': ' + value);
      +     }, log);
      +     expect(log).toEqual(['name: misko', 'gender:male']);
      +   
      + * + * @param {Object|Array} obj Object to iterate over. + * @param {Function} iterator Iterator function. + * @param {Object=} context Object to become context (`this`) for the iterator function. + * @returns {Object|Array} Reference to `obj`. + */ +function forEach(obj, iterator, context) { + var key; + if (obj) { + if (isFunction(obj)){ + for (key in obj) { + if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) { + iterator.call(context, obj[key], key); + } + } + } else if (obj.forEach && obj.forEach !== forEach) { + obj.forEach(iterator, context); + } else if (isArrayLike(obj)) { + for (key = 0; key < obj.length; key++) + iterator.call(context, obj[key], key); + } else { + for (key in obj) { + if (obj.hasOwnProperty(key)) { + iterator.call(context, obj[key], key); + } + } + } + } + return obj; +} + +function sortedKeys(obj) { + var keys = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys.sort(); +} + +function forEachSorted(obj, iterator, context) { + var keys = sortedKeys(obj); + for ( var i = 0; i < keys.length; i++) { + iterator.call(context, obj[keys[i]], keys[i]); + } + return keys; +} + + +/** + * when using forEach the params are value, key, but it is often useful to have key, value. + * @param {function(string, *)} iteratorFn + * @returns {function(*, string)} + */ +function reverseParams(iteratorFn) { + return function(value, key) { iteratorFn(key, value); }; +} + +/** + * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric + * characters such as '012ABC'. The reason why we are not using simply a number counter is that + * the number string gets longer over time, and it can also overflow, where as the nextId + * will grow much slower, it is a string, and it will never overflow. + * + * @returns an unique alpha-numeric string + */ +function nextUid() { + var index = uid.length; + var digit; + + while(index) { + index--; + digit = uid[index].charCodeAt(0); + if (digit == 57 /*'9'*/) { + uid[index] = 'A'; + return uid.join(''); + } + if (digit == 90 /*'Z'*/) { + uid[index] = '0'; + } else { + uid[index] = String.fromCharCode(digit + 1); + return uid.join(''); + } + } + uid.unshift('0'); + return uid.join(''); +} + + +/** + * Set or clear the hashkey for an object. + * @param obj object + * @param h the hashkey (!truthy to delete the hashkey) + */ +function setHashKey(obj, h) { + if (h) { + obj.$$hashKey = h; + } + else { + delete obj.$$hashKey; + } +} + +/** + * @ngdoc function + * @name angular.extend + * @function + * + * @description + * Extends the destination object `dst` by copying all of the properties from the `src` object(s) + * to `dst`. You can specify multiple `src` objects. + * + * @param {Object} dst Destination object. + * @param {...Object} src Source object(s). + * @returns {Object} Reference to `dst`. + */ +function extend(dst) { + var h = dst.$$hashKey; + forEach(arguments, function(obj){ + if (obj !== dst) { + forEach(obj, function(value, key){ + dst[key] = value; + }); + } + }); + + setHashKey(dst,h); + return dst; +} + +function int(str) { + return parseInt(str, 10); +} + + +function inherit(parent, extra) { + return extend(new (extend(function() {}, {prototype:parent}))(), extra); +} + +/** + * @ngdoc function + * @name angular.noop + * @function + * + * @description + * A function that performs no operations. This function can be useful when writing code in the + * functional style. +
      +     function foo(callback) {
      +       var result = calculateResult();
      +       (callback || angular.noop)(result);
      +     }
      +   
      + */ +function noop() {} +noop.$inject = []; + + +/** + * @ngdoc function + * @name angular.identity + * @function + * + * @description + * A function that returns its first argument. This function is useful when writing code in the + * functional style. + * +
      +     function transformer(transformationFn, value) {
      +       return (transformationFn || angular.identity)(value);
      +     };
      +   
      + */ +function identity($) {return $;} +identity.$inject = []; + + +function valueFn(value) {return function() {return value;};} + +/** + * @ngdoc function + * @name angular.isUndefined + * @function + * + * @description + * Determines if a reference is undefined. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is undefined. + */ +function isUndefined(value){return typeof value == 'undefined';} + + +/** + * @ngdoc function + * @name angular.isDefined + * @function + * + * @description + * Determines if a reference is defined. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is defined. + */ +function isDefined(value){return typeof value != 'undefined';} + + +/** + * @ngdoc function + * @name angular.isObject + * @function + * + * @description + * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not + * considered to be objects. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is an `Object` but not `null`. + */ +function isObject(value){return value != null && typeof value == 'object';} + + +/** + * @ngdoc function + * @name angular.isString + * @function + * + * @description + * Determines if a reference is a `String`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `String`. + */ +function isString(value){return typeof value == 'string';} + + +/** + * @ngdoc function + * @name angular.isNumber + * @function + * + * @description + * Determines if a reference is a `Number`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Number`. + */ +function isNumber(value){return typeof value == 'number';} + + +/** + * @ngdoc function + * @name angular.isDate + * @function + * + * @description + * Determines if a value is a date. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Date`. + */ +function isDate(value){ + return toString.apply(value) == '[object Date]'; +} + + +/** + * @ngdoc function + * @name angular.isArray + * @function + * + * @description + * Determines if a reference is an `Array`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is an `Array`. + */ +function isArray(value) { + return toString.apply(value) == '[object Array]'; +} + + +/** + * @ngdoc function + * @name angular.isFunction + * @function + * + * @description + * Determines if a reference is a `Function`. + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `Function`. + */ +function isFunction(value){return typeof value == 'function';} + + +/** + * Determines if a value is a regular expression object. + * + * @private + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a `RegExp`. + */ +function isRegExp(value) { + return toString.apply(value) == '[object RegExp]'; +} + + +/** + * Checks if `obj` is a window object. + * + * @private + * @param {*} obj Object to check + * @returns {boolean} True if `obj` is a window obj. + */ +function isWindow(obj) { + return obj && obj.document && obj.location && obj.alert && obj.setInterval; +} + + +function isScope(obj) { + return obj && obj.$evalAsync && obj.$watch; +} + + +function isFile(obj) { + return toString.apply(obj) === '[object File]'; +} + + +function isBoolean(value) { + return typeof value == 'boolean'; +} + + +var trim = (function() { + // native trim is way faster: http://jsperf.com/angular-trim-test + // but IE doesn't have it... :-( + // TODO: we should move this into IE/ES5 polyfill + if (!String.prototype.trim) { + return function(value) { + return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; + }; + } + return function(value) { + return isString(value) ? value.trim() : value; + }; +})(); + + +/** + * @ngdoc function + * @name angular.isElement + * @function + * + * @description + * Determines if a reference is a DOM element (or wrapped jQuery element). + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). + */ +function isElement(node) { + return node && + (node.nodeName // we are a direct element + || (node.on && node.find)); // we have an on and find method part of jQuery API +} + +/** + * @param str 'key1,key2,...' + * @returns {object} in the form of {key1:true, key2:true, ...} + */ +function makeMap(str){ + var obj = {}, items = str.split(","), i; + for ( i = 0; i < items.length; i++ ) + obj[ items[i] ] = true; + return obj; +} + + +if (msie < 9) { + nodeName_ = function(element) { + element = element.nodeName ? element : element[0]; + return (element.scopeName && element.scopeName != 'HTML') + ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; + }; +} else { + nodeName_ = function(element) { + return element.nodeName ? element.nodeName : element[0].nodeName; + }; +} + + +function map(obj, iterator, context) { + var results = []; + forEach(obj, function(value, index, list) { + results.push(iterator.call(context, value, index, list)); + }); + return results; +} + + +/** + * @description + * Determines the number of elements in an array, the number of properties an object has, or + * the length of a string. + * + * Note: This function is used to augment the Object type in Angular expressions. See + * {@link angular.Object} for more information about Angular arrays. + * + * @param {Object|Array|string} obj Object, array, or string to inspect. + * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object + * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. + */ +function size(obj, ownPropsOnly) { + var count = 0, key; + + if (isArray(obj) || isString(obj)) { + return obj.length; + } else if (isObject(obj)){ + for (key in obj) + if (!ownPropsOnly || obj.hasOwnProperty(key)) + count++; + } + + return count; +} + + +function includes(array, obj) { + return indexOf(array, obj) != -1; +} + +function indexOf(array, obj) { + if (array.indexOf) return array.indexOf(obj); + + for ( var i = 0; i < array.length; i++) { + if (obj === array[i]) return i; + } + return -1; +} + +function arrayRemove(array, value) { + var index = indexOf(array, value); + if (index >=0) + array.splice(index, 1); + return value; +} + +function isLeafNode (node) { + if (node) { + switch (node.nodeName) { + case "OPTION": + case "PRE": + case "TITLE": + return true; + } + } + return false; +} + +/** + * @ngdoc function + * @name angular.copy + * @function + * + * @description + * Creates a deep copy of `source`, which should be an object or an array. + * + * * If no destination is supplied, a copy of the object or array is created. + * * If a destination is provided, all of its elements (for array) or properties (for objects) + * are deleted and then all elements/properties from the source are copied to it. + * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. + * * If `source` is identical to 'destination' an exception will be thrown. + * + * @param {*} source The source that will be used to make a copy. + * Can be any type, including primitives, `null`, and `undefined`. + * @param {(Object|Array)=} destination Destination into which the source is copied. If + * provided, must be of the same type as `source`. + * @returns {*} The copy or updated `destination`, if `destination` was specified. + * + * @example + + +
      +
      + Name:
      + E-mail:
      + Gender: male + female
      + + +
      +
      form = {{user | json}}
      +
      master = {{master | json}}
      +
      + + +
      +
      + */ +function copy(source, destination){ + if (isWindow(source) || isScope(source)) { + throw ngMinErr('cpws', + "Can't copy! Making copies of Window or Scope instances is not supported."); + } + + if (!destination) { + destination = source; + if (source) { + if (isArray(source)) { + destination = copy(source, []); + } else if (isDate(source)) { + destination = new Date(source.getTime()); + } else if (isRegExp(source)) { + destination = new RegExp(source.source); + } else if (isObject(source)) { + destination = copy(source, {}); + } + } + } else { + if (source === destination) throw ngMinErr('cpi', + "Can't copy! Source and destination are identical."); + if (isArray(source)) { + destination.length = 0; + for ( var i = 0; i < source.length; i++) { + destination.push(copy(source[i])); + } + } else { + var h = destination.$$hashKey; + forEach(destination, function(value, key){ + delete destination[key]; + }); + for ( var key in source) { + destination[key] = copy(source[key]); + } + setHashKey(destination,h); + } + } + return destination; +} + +/** + * Create a shallow copy of an object + */ +function shallowCopy(src, dst) { + dst = dst || {}; + + for(var key in src) { + // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src + // so we don't need to worry hasOwnProperty here + if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') { + dst[key] = src[key]; + } + } + + return dst; +} + + +/** + * @ngdoc function + * @name angular.equals + * @function + * + * @description + * Determines if two objects or two values are equivalent. Supports value types, regular + * expressions, arrays and objects. + * + * Two objects or values are considered equivalent if at least one of the following is true: + * + * * Both objects or values pass `===` comparison. + * * Both objects or values are of the same type and all of their properties are equal by + * comparing them with `angular.equals`. + * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) + * * Both values represent the same regular expression (In JavasScript, + * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual + * representation matches). + * + * During a property comparison, properties of `function` type and properties with names + * that begin with `$` are ignored. + * + * Scope and DOMWindow objects are being compared only by identify (`===`). + * + * @param {*} o1 Object or value to compare. + * @param {*} o2 Object or value to compare. + * @returns {boolean} True if arguments are equal. + */ +function equals(o1, o2) { + if (o1 === o2) return true; + if (o1 === null || o2 === null) return false; + if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN + var t1 = typeof o1, t2 = typeof o2, length, key, keySet; + if (t1 == t2) { + if (t1 == 'object') { + if (isArray(o1)) { + if (!isArray(o2)) return false; + if ((length = o1.length) == o2.length) { + for(key=0; key 2 ? sliceArgs(arguments, 2) : []; + if (isFunction(fn) && !(fn instanceof RegExp)) { + return curryArgs.length + ? function() { + return arguments.length + ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) + : fn.apply(self, curryArgs); + } + : function() { + return arguments.length + ? fn.apply(self, arguments) + : fn.call(self); + }; + } else { + // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) + return fn; + } +} + + +function toJsonReplacer(key, value) { + var val = value; + + if (typeof key === 'string' && key.charAt(0) === '$') { + val = undefined; + } else if (isWindow(value)) { + val = '$WINDOW'; + } else if (value && document === value) { + val = '$DOCUMENT'; + } else if (isScope(value)) { + val = '$SCOPE'; + } + + return val; +} + + +/** + * @ngdoc function + * @name angular.toJson + * @function + * + * @description + * Serializes input into a JSON-formatted string. Properties with leading $ characters will be + * stripped since angular uses this notation internally. + * + * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. + * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. + * @returns {string|undefined} JSON-ified string representing `obj`. + */ +function toJson(obj, pretty) { + if (typeof obj === 'undefined') return undefined; + return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); +} + + +/** + * @ngdoc function + * @name angular.fromJson + * @function + * + * @description + * Deserializes a JSON string. + * + * @param {string} json JSON string to deserialize. + * @returns {Object|Array|Date|string|number} Deserialized thingy. + */ +function fromJson(json) { + return isString(json) + ? JSON.parse(json) + : json; +} + + +function toBoolean(value) { + if (value && value.length !== 0) { + var v = lowercase("" + value); + value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); + } else { + value = false; + } + return value; +} + +/** + * @returns {string} Returns the string representation of the element. + */ +function startingTag(element) { + element = jqLite(element).clone(); + try { + // turns out IE does not let you set .html() on elements which + // are not allowed to have children. So we just ignore it. + element.html(''); + } catch(e) {} + // As Per DOM Standards + var TEXT_NODE = 3; + var elemHtml = jqLite('
      ').append(element).html(); + try { + return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : + elemHtml. + match(/^(<[^>]+>)/)[1]. + replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); + } catch(e) { + return lowercase(elemHtml); + } + +} + + +///////////////////////////////////////////////// + +/** + * Tries to decode the URI component without throwing an exception. + * + * @private + * @param str value potential URI component to check. + * @returns {boolean} True if `value` can be decoded + * with the decodeURIComponent function. + */ +function tryDecodeURIComponent(value) { + try { + return decodeURIComponent(value); + } catch(e) { + // Ignore any invalid uri component + } +} + + +/** + * Parses an escaped url query string into key-value pairs. + * @returns Object.<(string|boolean)> + */ +function parseKeyValue(/**string*/keyValue) { + var obj = {}, key_value, key; + forEach((keyValue || "").split('&'), function(keyValue){ + if ( keyValue ) { + key_value = keyValue.split('='); + key = tryDecodeURIComponent(key_value[0]); + if ( isDefined(key) ) { + var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; + if (!obj[key]) { + obj[key] = val; + } else if(isArray(obj[key])) { + obj[key].push(val); + } else { + obj[key] = [obj[key],val]; + } + } + } + }); + return obj; +} + +function toKeyValue(obj) { + var parts = []; + forEach(obj, function(value, key) { + if (isArray(value)) { + forEach(value, function(arrayValue) { + parts.push(encodeUriQuery(key, true) + + (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); + }); + } else { + parts.push(encodeUriQuery(key, true) + + (value === true ? '' : '=' + encodeUriQuery(value, true))); + } + }); + return parts.length ? parts.join('&') : ''; +} + + +/** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ +function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); +} + + +/** + * This method is intended for encoding *key* or *value* parts of query component. We need a custom + * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be + * encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ +function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); +} + + +/** + * @ngdoc directive + * @name ng.directive:ngApp + * + * @element ANY + * @param {angular.Module} ngApp an optional application + * {@link angular.module module} name to load. + * + * @description + * + * Use this directive to auto-bootstrap an application. Only + * one ngApp directive can be used per HTML document. The directive + * designates the root of the application and is typically placed + * at the root of the page. + * + * The first ngApp found in the document will be auto-bootstrapped. To use multiple applications in + * an HTML document you must manually bootstrap them using {@link angular.bootstrap}. + * Applications cannot be nested. + * + * In the example below if the `ngApp` directive were not placed + * on the `html` element then the document would not be compiled + * and the `{{ 1+2 }}` would not be resolved to `3`. + * + * `ngApp` is the easiest way to bootstrap an application. + * + + + I can add: 1 + 2 = {{ 1+2 }} + + + * + */ +function angularInit(element, bootstrap) { + var elements = [element], + appElement, + module, + names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], + NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; + + function append(element) { + element && elements.push(element); + } + + forEach(names, function(name) { + names[name] = true; + append(document.getElementById(name)); + name = name.replace(':', '\\:'); + if (element.querySelectorAll) { + forEach(element.querySelectorAll('.' + name), append); + forEach(element.querySelectorAll('.' + name + '\\:'), append); + forEach(element.querySelectorAll('[' + name + ']'), append); + } + }); + + forEach(elements, function(element) { + if (!appElement) { + var className = ' ' + element.className + ' '; + var match = NG_APP_CLASS_REGEXP.exec(className); + if (match) { + appElement = element; + module = (match[2] || '').replace(/\s+/g, ','); + } else { + forEach(element.attributes, function(attr) { + if (!appElement && names[attr.name]) { + appElement = element; + module = attr.value; + } + }); + } + } + }); + if (appElement) { + bootstrap(appElement, module ? [module] : []); + } +} + +/** + * @ngdoc function + * @name angular.bootstrap + * @description + * Use this function to manually start up angular application. + * + * See: {@link guide/bootstrap Bootstrap} + * + * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. + * They must use {@link api/ng.directive:ngApp ngApp}. + * + * @param {Element} element DOM element which is the root of angular application. + * @param {Array=} modules an array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * See: {@link angular.module modules} + * @returns {AUTO.$injector} Returns the newly created injector for this app. + */ +function bootstrap(element, modules) { + var doBootstrap = function() { + element = jqLite(element); + + if (element.injector()) { + var tag = (element[0] === document) ? 'document' : startingTag(element); + throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); + } + + modules = modules || []; + modules.unshift(['$provide', function($provide) { + $provide.value('$rootElement', element); + }]); + modules.unshift('ng'); + var injector = createInjector(modules); + injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', + function(scope, element, compile, injector, animate) { + scope.$apply(function() { + element.data('$injector', injector); + compile(element)(scope); + }); + }] + ); + return injector; + }; + + var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; + + if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { + return doBootstrap(); + } + + window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); + angular.resumeBootstrap = function(extraModules) { + forEach(extraModules, function(module) { + modules.push(module); + }); + doBootstrap(); + }; +} + +var SNAKE_CASE_REGEXP = /[A-Z]/g; +function snake_case(name, separator){ + separator = separator || '_'; + return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); +} + +function bindJQuery() { + // bind to jQuery if present; + jQuery = window.jQuery; + // reset to jQuery or default to us. + if (jQuery) { + jqLite = jQuery; + extend(jQuery.fn, { + scope: JQLitePrototype.scope, + isolateScope: JQLitePrototype.isolateScope, + controller: JQLitePrototype.controller, + injector: JQLitePrototype.injector, + inheritedData: JQLitePrototype.inheritedData + }); + // Method signature: + // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) + jqLitePatchJQueryRemove('remove', true, true, false); + jqLitePatchJQueryRemove('empty', false, false, false); + jqLitePatchJQueryRemove('html', false, false, true); + } else { + jqLite = JQLite; + } + angular.element = jqLite; +} + +/** + * throw error if the argument is falsy. + */ +function assertArg(arg, name, reason) { + if (!arg) { + throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); + } + return arg; +} + +function assertArgFn(arg, name, acceptArrayAnnotation) { + if (acceptArrayAnnotation && isArray(arg)) { + arg = arg[arg.length - 1]; + } + + assertArg(isFunction(arg), name, 'not a function, got ' + + (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); + return arg; +} + +/** + * throw error if the name given is hasOwnProperty + * @param {String} name the name to test + * @param {String} context the context in which the name is used, such as module or directive + */ +function assertNotHasOwnProperty(name, context) { + if (name === 'hasOwnProperty') { + throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); + } +} + +/** + * Return the value accessible from the object by path. Any undefined traversals are ignored + * @param {Object} obj starting object + * @param {string} path path to traverse + * @param {boolean=true} bindFnToScope + * @returns value as accessible by path + */ +//TODO(misko): this function needs to be removed +function getter(obj, path, bindFnToScope) { + if (!path) return obj; + var keys = path.split('.'); + var key; + var lastInstance = obj; + var len = keys.length; + + for (var i = 0; i < len; i++) { + key = keys[i]; + if (obj) { + obj = (lastInstance = obj)[key]; + } + } + if (!bindFnToScope && isFunction(obj)) { + return bind(lastInstance, obj); + } + return obj; +} + +/** + * Return the siblings between `startNode` and `endNode`, inclusive + * @param {Object} object with `startNode` and `endNode` properties + * @returns jQlite object containing the elements + */ +function getBlockElements(block) { + if (block.startNode === block.endNode) { + return jqLite(block.startNode); + } + + var element = block.startNode; + var elements = [element]; + + do { + element = element.nextSibling; + if (!element) break; + elements.push(element); + } while (element !== block.endNode); + + return jqLite(elements); +} + +/** + * @ngdoc interface + * @name angular.Module + * @description + * + * Interface for configuring angular {@link angular.module modules}. + */ + +function setupModuleLoader(window) { + + var $injectorMinErr = minErr('$injector'); + + function ensure(obj, name, factory) { + return obj[name] || (obj[name] = factory()); + } + + return ensure(ensure(window, 'angular', Object), 'module', function() { + /** @type {Object.} */ + var modules = {}; + + /** + * @ngdoc function + * @name angular.module + * @description + * + * The `angular.module` is a global place for creating, registering and retrieving Angular + * modules. + * All modules (angular core or 3rd party) that should be available to an application must be + * registered using this mechanism. + * + * When passed two or more arguments, a new module is created. If passed only one argument, an + * existing module (the name passed as the first argument to `module`) is retrieved. + * + * + * # Module + * + * A module is a collection of services, directives, filters, and configuration information. + * `angular.module` is used to configure the {@link AUTO.$injector $injector}. + * + *
      +     * // Create a new module
      +     * var myModule = angular.module('myModule', []);
      +     *
      +     * // register a new service
      +     * myModule.value('appName', 'MyCoolApp');
      +     *
      +     * // configure existing services inside initialization blocks.
      +     * myModule.config(function($locationProvider) {
      +     *   // Configure existing providers
      +     *   $locationProvider.hashPrefix('!');
      +     * });
      +     * 
      + * + * Then you can create an injector and load your modules like this: + * + *
      +     * var injector = angular.injector(['ng', 'MyModule'])
      +     * 
      + * + * However it's more likely that you'll just use + * {@link ng.directive:ngApp ngApp} or + * {@link angular.bootstrap} to simplify this process for you. + * + * @param {!string} name The name of the module to create or retrieve. + * @param {Array.=} requires If specified then new module is being created. If + * unspecified then the the module is being retrieved for further configuration. + * @param {Function} configFn Optional configuration function for the module. Same as + * {@link angular.Module#methods_config Module#config()}. + * @returns {module} new module with the {@link angular.Module} api. + */ + return function module(name, requires, configFn) { + assertNotHasOwnProperty(name, 'module'); + if (requires && modules.hasOwnProperty(name)) { + modules[name] = null; + } + return ensure(modules, name, function() { + if (!requires) { + throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + + "the module name or forgot to load it. If registering a module ensure that you " + + "specify the dependencies as the second argument.", name); + } + + /** @type {!Array.>} */ + var invokeQueue = []; + + /** @type {!Array.} */ + var runBlocks = []; + + var config = invokeLater('$injector', 'invoke'); + + /** @type {angular.Module} */ + var moduleInstance = { + // Private state + _invokeQueue: invokeQueue, + _runBlocks: runBlocks, + + /** + * @ngdoc property + * @name angular.Module#requires + * @propertyOf angular.Module + * @returns {Array.} List of module names which must be loaded before this module. + * @description + * Holds the list of modules which the injector will load before the current module is + * loaded. + */ + requires: requires, + + /** + * @ngdoc property + * @name angular.Module#name + * @propertyOf angular.Module + * @returns {string} Name of the module. + * @description + */ + name: name, + + + /** + * @ngdoc method + * @name angular.Module#provider + * @methodOf angular.Module + * @param {string} name service name + * @param {Function} providerType Construction function for creating new instance of the + * service. + * @description + * See {@link AUTO.$provide#provider $provide.provider()}. + */ + provider: invokeLater('$provide', 'provider'), + + /** + * @ngdoc method + * @name angular.Module#factory + * @methodOf angular.Module + * @param {string} name service name + * @param {Function} providerFunction Function for creating new instance of the service. + * @description + * See {@link AUTO.$provide#factory $provide.factory()}. + */ + factory: invokeLater('$provide', 'factory'), + + /** + * @ngdoc method + * @name angular.Module#service + * @methodOf angular.Module + * @param {string} name service name + * @param {Function} constructor A constructor function that will be instantiated. + * @description + * See {@link AUTO.$provide#service $provide.service()}. + */ + service: invokeLater('$provide', 'service'), + + /** + * @ngdoc method + * @name angular.Module#value + * @methodOf angular.Module + * @param {string} name service name + * @param {*} object Service instance object. + * @description + * See {@link AUTO.$provide#value $provide.value()}. + */ + value: invokeLater('$provide', 'value'), + + /** + * @ngdoc method + * @name angular.Module#constant + * @methodOf angular.Module + * @param {string} name constant name + * @param {*} object Constant value. + * @description + * Because the constant are fixed, they get applied before other provide methods. + * See {@link AUTO.$provide#constant $provide.constant()}. + */ + constant: invokeLater('$provide', 'constant', 'unshift'), + + /** + * @ngdoc method + * @name angular.Module#animation + * @methodOf angular.Module + * @param {string} name animation name + * @param {Function} animationFactory Factory function for creating new instance of an + * animation. + * @description + * + * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. + * + * + * Defines an animation hook that can be later used with + * {@link ngAnimate.$animate $animate} service and directives that use this service. + * + *
      +           * module.animation('.animation-name', function($inject1, $inject2) {
      +           *   return {
      +           *     eventName : function(element, done) {
      +           *       //code to run the animation
      +           *       //once complete, then run done()
      +           *       return function cancellationFunction(element) {
      +           *         //code to cancel the animation
      +           *       }
      +           *     }
      +           *   }
      +           * })
      +           * 
      + * + * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and + * {@link ngAnimate ngAnimate module} for more information. + */ + animation: invokeLater('$animateProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#filter + * @methodOf angular.Module + * @param {string} name Filter name. + * @param {Function} filterFactory Factory function for creating new instance of filter. + * @description + * See {@link ng.$filterProvider#register $filterProvider.register()}. + */ + filter: invokeLater('$filterProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#controller + * @methodOf angular.Module + * @param {string|Object} name Controller name, or an object map of controllers where the + * keys are the names and the values are the constructors. + * @param {Function} constructor Controller constructor function. + * @description + * See {@link ng.$controllerProvider#register $controllerProvider.register()}. + */ + controller: invokeLater('$controllerProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#directive + * @methodOf angular.Module + * @param {string|Object} name Directive name, or an object map of directives where the + * keys are the names and the values are the factories. + * @param {Function} directiveFactory Factory function for creating new instance of + * directives. + * @description + * See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}. + */ + directive: invokeLater('$compileProvider', 'directive'), + + /** + * @ngdoc method + * @name angular.Module#config + * @methodOf angular.Module + * @param {Function} configFn Execute this function on module load. Useful for service + * configuration. + * @description + * Use this method to register work which needs to be performed on module loading. + */ + config: config, + + /** + * @ngdoc method + * @name angular.Module#run + * @methodOf angular.Module + * @param {Function} initializationFn Execute this function after injector creation. + * Useful for application initialization. + * @description + * Use this method to register work which should be performed when the injector is done + * loading all modules. + */ + run: function(block) { + runBlocks.push(block); + return this; + } + }; + + if (configFn) { + config(configFn); + } + + return moduleInstance; + + /** + * @param {string} provider + * @param {string} method + * @param {String=} insertMethod + * @returns {angular.Module} + */ + function invokeLater(provider, method, insertMethod) { + return function() { + invokeQueue[insertMethod || 'push']([provider, method, arguments]); + return moduleInstance; + }; + } + }); + }; + }); + +} + +/* global + angularModule: true, + version: true, + + $LocaleProvider, + $CompileProvider, + + htmlAnchorDirective, + inputDirective, + inputDirective, + formDirective, + scriptDirective, + selectDirective, + styleDirective, + optionDirective, + ngBindDirective, + ngBindHtmlDirective, + ngBindTemplateDirective, + ngClassDirective, + ngClassEvenDirective, + ngClassOddDirective, + ngCspDirective, + ngCloakDirective, + ngControllerDirective, + ngFormDirective, + ngHideDirective, + ngIfDirective, + ngIncludeDirective, + ngInitDirective, + ngNonBindableDirective, + ngPluralizeDirective, + ngRepeatDirective, + ngShowDirective, + ngStyleDirective, + ngSwitchDirective, + ngSwitchWhenDirective, + ngSwitchDefaultDirective, + ngOptionsDirective, + ngTranscludeDirective, + ngModelDirective, + ngListDirective, + ngChangeDirective, + requiredDirective, + requiredDirective, + ngValueDirective, + ngAttributeAliasDirectives, + ngEventDirectives, + + $AnchorScrollProvider, + $AnimateProvider, + $BrowserProvider, + $CacheFactoryProvider, + $ControllerProvider, + $DocumentProvider, + $ExceptionHandlerProvider, + $FilterProvider, + $InterpolateProvider, + $IntervalProvider, + $HttpProvider, + $HttpBackendProvider, + $LocationProvider, + $LogProvider, + $ParseProvider, + $RootScopeProvider, + $QProvider, + $SceProvider, + $SceDelegateProvider, + $SnifferProvider, + $TemplateCacheProvider, + $TimeoutProvider, + $WindowProvider +*/ + + +/** + * @ngdoc property + * @name angular.version + * @description + * An object that contains information about the current AngularJS version. This object has the + * following properties: + * + * - `full` – `{string}` – Full version string, such as "0.9.18". + * - `major` – `{number}` – Major version number, such as "0". + * - `minor` – `{number}` – Minor version number, such as "9". + * - `dot` – `{number}` – Dot version number, such as "18". + * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". + */ +var version = { + full: '1.2.0', // all of these placeholder strings will be replaced by grunt's + major: 1, // package task + minor: "NG_VERSION_MINOR", + dot: 0, + codeName: 'timely-delivery' +}; + + +function publishExternalAPI(angular){ + extend(angular, { + 'bootstrap': bootstrap, + 'copy': copy, + 'extend': extend, + 'equals': equals, + 'element': jqLite, + 'forEach': forEach, + 'injector': createInjector, + 'noop':noop, + 'bind':bind, + 'toJson': toJson, + 'fromJson': fromJson, + 'identity':identity, + 'isUndefined': isUndefined, + 'isDefined': isDefined, + 'isString': isString, + 'isFunction': isFunction, + 'isObject': isObject, + 'isNumber': isNumber, + 'isElement': isElement, + 'isArray': isArray, + 'version': version, + 'isDate': isDate, + 'lowercase': lowercase, + 'uppercase': uppercase, + 'callbacks': {counter: 0}, + '$$minErr': minErr, + '$$csp': csp + }); + + angularModule = setupModuleLoader(window); + try { + angularModule('ngLocale'); + } catch (e) { + angularModule('ngLocale', []).provider('$locale', $LocaleProvider); + } + + angularModule('ng', ['ngLocale'], ['$provide', + function ngModule($provide) { + $provide.provider('$compile', $CompileProvider). + directive({ + a: htmlAnchorDirective, + input: inputDirective, + textarea: inputDirective, + form: formDirective, + script: scriptDirective, + select: selectDirective, + style: styleDirective, + option: optionDirective, + ngBind: ngBindDirective, + ngBindHtml: ngBindHtmlDirective, + ngBindTemplate: ngBindTemplateDirective, + ngClass: ngClassDirective, + ngClassEven: ngClassEvenDirective, + ngClassOdd: ngClassOddDirective, + ngCloak: ngCloakDirective, + ngController: ngControllerDirective, + ngForm: ngFormDirective, + ngHide: ngHideDirective, + ngIf: ngIfDirective, + ngInclude: ngIncludeDirective, + ngInit: ngInitDirective, + ngNonBindable: ngNonBindableDirective, + ngPluralize: ngPluralizeDirective, + ngRepeat: ngRepeatDirective, + ngShow: ngShowDirective, + ngStyle: ngStyleDirective, + ngSwitch: ngSwitchDirective, + ngSwitchWhen: ngSwitchWhenDirective, + ngSwitchDefault: ngSwitchDefaultDirective, + ngOptions: ngOptionsDirective, + ngTransclude: ngTranscludeDirective, + ngModel: ngModelDirective, + ngList: ngListDirective, + ngChange: ngChangeDirective, + required: requiredDirective, + ngRequired: requiredDirective, + ngValue: ngValueDirective + }). + directive(ngAttributeAliasDirectives). + directive(ngEventDirectives); + $provide.provider({ + $anchorScroll: $AnchorScrollProvider, + $animate: $AnimateProvider, + $browser: $BrowserProvider, + $cacheFactory: $CacheFactoryProvider, + $controller: $ControllerProvider, + $document: $DocumentProvider, + $exceptionHandler: $ExceptionHandlerProvider, + $filter: $FilterProvider, + $interpolate: $InterpolateProvider, + $interval: $IntervalProvider, + $http: $HttpProvider, + $httpBackend: $HttpBackendProvider, + $location: $LocationProvider, + $log: $LogProvider, + $parse: $ParseProvider, + $rootScope: $RootScopeProvider, + $q: $QProvider, + $sce: $SceProvider, + $sceDelegate: $SceDelegateProvider, + $sniffer: $SnifferProvider, + $templateCache: $TemplateCacheProvider, + $timeout: $TimeoutProvider, + $window: $WindowProvider + }); + } + ]); +} + +/* global + + -JQLitePrototype, + -addEventListenerFn, + -removeEventListenerFn, + -BOOLEAN_ATTR +*/ + +////////////////////////////////// +//JQLite +////////////////////////////////// + +/** + * @ngdoc function + * @name angular.element + * @function + * + * @description + * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. + * + * If jQuery is available, `angular.element` is an alias for the + * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` + * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." + * + *
      jqLite is a tiny, API-compatible subset of jQuery that allows + * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most + * commonly needed functionality with the goal of having a very small footprint.
      + * + * To use jQuery, simply load it before `DOMContentLoaded` event fired. + * + *
      **Note:** all element references in Angular are always wrapped with jQuery or + * jqLite; they are never raw DOM references.
      + * + * ## Angular's jqLite + * jqLite provides only the following jQuery methods: + * + * - [`addClass()`](http://api.jquery.com/addClass/) + * - [`after()`](http://api.jquery.com/after/) + * - [`append()`](http://api.jquery.com/append/) + * - [`attr()`](http://api.jquery.com/attr/) + * - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData + * - [`children()`](http://api.jquery.com/children/) - Does not support selectors + * - [`clone()`](http://api.jquery.com/clone/) + * - [`contents()`](http://api.jquery.com/contents/) + * - [`css()`](http://api.jquery.com/css/) + * - [`data()`](http://api.jquery.com/data/) + * - [`eq()`](http://api.jquery.com/eq/) + * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name + * - [`hasClass()`](http://api.jquery.com/hasClass/) + * - [`html()`](http://api.jquery.com/html/) + * - [`next()`](http://api.jquery.com/next/) - Does not support selectors + * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData + * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors + * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors + * - [`prepend()`](http://api.jquery.com/prepend/) + * - [`prop()`](http://api.jquery.com/prop/) + * - [`ready()`](http://api.jquery.com/ready/) + * - [`remove()`](http://api.jquery.com/remove/) + * - [`removeAttr()`](http://api.jquery.com/removeAttr/) + * - [`removeClass()`](http://api.jquery.com/removeClass/) + * - [`removeData()`](http://api.jquery.com/removeData/) + * - [`replaceWith()`](http://api.jquery.com/replaceWith/) + * - [`text()`](http://api.jquery.com/text/) + * - [`toggleClass()`](http://api.jquery.com/toggleClass/) + * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. + * - [`unbind()`](http://api.jquery.com/off/) - Does not support namespaces + * - [`val()`](http://api.jquery.com/val/) + * - [`wrap()`](http://api.jquery.com/wrap/) + * + * ## jQuery/jqLite Extras + * Angular also provides the following additional methods and events to both jQuery and jqLite: + * + * ### Events + * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event + * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM + * element before it is removed. + * + * ### Methods + * - `controller(name)` - retrieves the controller of the current element or its parent. By default + * retrieves controller associated with the `ngController` directive. If `name` is provided as + * camelCase directive name, then the controller for this directive will be retrieved (e.g. + * `'ngModel'`). + * - `injector()` - retrieves the injector of the current element or its parent. + * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current + * element or its parent. + * - `isolateScope()` - retrieves an isolate {@link api/ng.$rootScope.Scope scope} if one is attached directly to the + * current element. This getter should be used only on elements that contain a directive which starts a new isolate + * scope. Calling `scope()` on this element always returns the original non-isolate scope. + * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top + * parent element is reached. + * + * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. + * @returns {Object} jQuery object. + */ + +var jqCache = JQLite.cache = {}, + jqName = JQLite.expando = 'ng-' + new Date().getTime(), + jqId = 1, + addEventListenerFn = (window.document.addEventListener + ? function(element, type, fn) {element.addEventListener(type, fn, false);} + : function(element, type, fn) {element.attachEvent('on' + type, fn);}), + removeEventListenerFn = (window.document.removeEventListener + ? function(element, type, fn) {element.removeEventListener(type, fn, false); } + : function(element, type, fn) {element.detachEvent('on' + type, fn); }); + +function jqNextId() { return ++jqId; } + + +var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; +var MOZ_HACK_REGEXP = /^moz([A-Z])/; +var jqLiteMinErr = minErr('jqLite'); + +/** + * Converts snake_case to camelCase. + * Also there is special case for Moz prefix starting with upper case letter. + * @param name Name to normalize + */ +function camelCase(name) { + return name. + replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { + return offset ? letter.toUpperCase() : letter; + }). + replace(MOZ_HACK_REGEXP, 'Moz$1'); +} + +///////////////////////////////////////////// +// jQuery mutation patch +// +// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a +// $destroy event on all DOM nodes being removed. +// +///////////////////////////////////////////// + +function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { + var originalJqFn = jQuery.fn[name]; + originalJqFn = originalJqFn.$original || originalJqFn; + removePatch.$original = originalJqFn; + jQuery.fn[name] = removePatch; + + function removePatch(param) { + // jshint -W040 + var list = filterElems && param ? [this.filter(param)] : [this], + fireEvent = dispatchThis, + set, setIndex, setLength, + element, childIndex, childLength, children; + + if (!getterIfNoArguments || param != null) { + while(list.length) { + set = list.shift(); + for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { + element = jqLite(set[setIndex]); + if (fireEvent) { + element.triggerHandler('$destroy'); + } else { + fireEvent = !fireEvent; + } + for(childIndex = 0, childLength = (children = element.children()).length; + childIndex < childLength; + childIndex++) { + list.push(jQuery(children[childIndex])); + } + } + } + } + return originalJqFn.apply(this, arguments); + } +} + +///////////////////////////////////////////// +function JQLite(element) { + if (element instanceof JQLite) { + return element; + } + if (!(this instanceof JQLite)) { + if (isString(element) && element.charAt(0) != '<') { + throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); + } + return new JQLite(element); + } + + if (isString(element)) { + var div = document.createElement('div'); + // Read about the NoScope elements here: + // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx + div.innerHTML = '
       
      ' + element; // IE insanity to make NoScope elements work! + div.removeChild(div.firstChild); // remove the superfluous div + jqLiteAddNodes(this, div.childNodes); + var fragment = jqLite(document.createDocumentFragment()); + fragment.append(this); // detach the elements from the temporary DOM div. + } else { + jqLiteAddNodes(this, element); + } +} + +function jqLiteClone(element) { + return element.cloneNode(true); +} + +function jqLiteDealoc(element){ + jqLiteRemoveData(element); + for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { + jqLiteDealoc(children[i]); + } +} + +function jqLiteOff(element, type, fn, unsupported) { + if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); + + var events = jqLiteExpandoStore(element, 'events'), + handle = jqLiteExpandoStore(element, 'handle'); + + if (!handle) return; //no listeners registered + + if (isUndefined(type)) { + forEach(events, function(eventHandler, type) { + removeEventListenerFn(element, type, eventHandler); + delete events[type]; + }); + } else { + forEach(type.split(' '), function(type) { + if (isUndefined(fn)) { + removeEventListenerFn(element, type, events[type]); + delete events[type]; + } else { + arrayRemove(events[type] || [], fn); + } + }); + } +} + +function jqLiteRemoveData(element, name) { + var expandoId = element[jqName], + expandoStore = jqCache[expandoId]; + + if (expandoStore) { + if (name) { + delete jqCache[expandoId].data[name]; + return; + } + + if (expandoStore.handle) { + expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); + jqLiteOff(element); + } + delete jqCache[expandoId]; + element[jqName] = undefined; // ie does not allow deletion of attributes on elements. + } +} + +function jqLiteExpandoStore(element, key, value) { + var expandoId = element[jqName], + expandoStore = jqCache[expandoId || -1]; + + if (isDefined(value)) { + if (!expandoStore) { + element[jqName] = expandoId = jqNextId(); + expandoStore = jqCache[expandoId] = {}; + } + expandoStore[key] = value; + } else { + return expandoStore && expandoStore[key]; + } +} + +function jqLiteData(element, key, value) { + var data = jqLiteExpandoStore(element, 'data'), + isSetter = isDefined(value), + keyDefined = !isSetter && isDefined(key), + isSimpleGetter = keyDefined && !isObject(key); + + if (!data && !isSimpleGetter) { + jqLiteExpandoStore(element, 'data', data = {}); + } + + if (isSetter) { + data[key] = value; + } else { + if (keyDefined) { + if (isSimpleGetter) { + // don't create data in this case. + return data && data[key]; + } else { + extend(data, key); + } + } else { + return data; + } + } +} + +function jqLiteHasClass(element, selector) { + if (!element.getAttribute) return false; + return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). + indexOf( " " + selector + " " ) > -1); +} + +function jqLiteRemoveClass(element, cssClasses) { + if (cssClasses && element.setAttribute) { + forEach(cssClasses.split(' '), function(cssClass) { + element.setAttribute('class', trim( + (" " + (element.getAttribute('class') || '') + " ") + .replace(/[\n\t]/g, " ") + .replace(" " + trim(cssClass) + " ", " ")) + ); + }); + } +} + +function jqLiteAddClass(element, cssClasses) { + if (cssClasses && element.setAttribute) { + var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') + .replace(/[\n\t]/g, " "); + + forEach(cssClasses.split(' '), function(cssClass) { + cssClass = trim(cssClass); + if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { + existingClasses += cssClass + ' '; + } + }); + + element.setAttribute('class', trim(existingClasses)); + } +} + +function jqLiteAddNodes(root, elements) { + if (elements) { + elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) + ? elements + : [ elements ]; + for(var i=0; i < elements.length; i++) { + root.push(elements[i]); + } + } +} + +function jqLiteController(element, name) { + return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); +} + +function jqLiteInheritedData(element, name, value) { + element = jqLite(element); + + // if element is the document object work with the html element instead + // this makes $(document).scope() possible + if(element[0].nodeType == 9) { + element = element.find('html'); + } + var names = isArray(name) ? name : [name]; + + while (element.length) { + + for (var i = 0, ii = names.length; i < ii; i++) { + if ((value = element.data(names[i])) !== undefined) return value; + } + element = element.parent(); + } +} + +////////////////////////////////////////// +// Functions which are declared directly. +////////////////////////////////////////// +var JQLitePrototype = JQLite.prototype = { + ready: function(fn) { + var fired = false; + + function trigger() { + if (fired) return; + fired = true; + fn(); + } + + // check if document already is loaded + if (document.readyState === 'complete'){ + setTimeout(trigger); + } else { + this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 + // we can not use jqLite since we are not done loading and jQuery could be loaded later. + // jshint -W064 + JQLite(window).on('load', trigger); // fallback to window.onload for others + // jshint +W064 + } + }, + toString: function() { + var value = []; + forEach(this, function(e){ value.push('' + e);}); + return '[' + value.join(', ') + ']'; + }, + + eq: function(index) { + return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); + }, + + length: 0, + push: push, + sort: [].sort, + splice: [].splice +}; + +////////////////////////////////////////// +// Functions iterating getter/setters. +// these functions return self on setter and +// value on get. +////////////////////////////////////////// +var BOOLEAN_ATTR = {}; +forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { + BOOLEAN_ATTR[lowercase(value)] = value; +}); +var BOOLEAN_ELEMENTS = {}; +forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { + BOOLEAN_ELEMENTS[uppercase(value)] = true; +}); + +function getBooleanAttrName(element, name) { + // check dom last since we will most likely fail on name + var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; + + // booleanAttr is here twice to minimize DOM access + return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; +} + +forEach({ + data: jqLiteData, + inheritedData: jqLiteInheritedData, + + scope: function(element) { + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); + }, + + isolateScope: function(element) { + // Can't use jqLiteData here directly so we stay compatible with jQuery! + return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); + }, + + controller: jqLiteController , + + injector: function(element) { + return jqLiteInheritedData(element, '$injector'); + }, + + removeAttr: function(element,name) { + element.removeAttribute(name); + }, + + hasClass: jqLiteHasClass, + + css: function(element, name, value) { + name = camelCase(name); + + if (isDefined(value)) { + element.style[name] = value; + } else { + var val; + + if (msie <= 8) { + // this is some IE specific weirdness that jQuery 1.6.4 does not sure why + val = element.currentStyle && element.currentStyle[name]; + if (val === '') val = 'auto'; + } + + val = val || element.style[name]; + + if (msie <= 8) { + // jquery weirdness :-/ + val = (val === '') ? undefined : val; + } + + return val; + } + }, + + attr: function(element, name, value){ + var lowercasedName = lowercase(name); + if (BOOLEAN_ATTR[lowercasedName]) { + if (isDefined(value)) { + if (!!value) { + element[name] = true; + element.setAttribute(name, lowercasedName); + } else { + element[name] = false; + element.removeAttribute(lowercasedName); + } + } else { + return (element[name] || + (element.attributes.getNamedItem(name)|| noop).specified) + ? lowercasedName + : undefined; + } + } else if (isDefined(value)) { + element.setAttribute(name, value); + } else if (element.getAttribute) { + // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code + // some elements (e.g. Document) don't have get attribute, so return undefined + var ret = element.getAttribute(name, 2); + // normalize non-existing attributes to undefined (as jQuery) + return ret === null ? undefined : ret; + } + }, + + prop: function(element, name, value) { + if (isDefined(value)) { + element[name] = value; + } else { + return element[name]; + } + }, + + text: (function() { + var NODE_TYPE_TEXT_PROPERTY = []; + if (msie < 9) { + NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ + NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ + } else { + NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ + NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ + } + getText.$dv = ''; + return getText; + + function getText(element, value) { + var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; + if (isUndefined(value)) { + return textProp ? element[textProp] : ''; + } + element[textProp] = value; + } + })(), + + val: function(element, value) { + if (isUndefined(value)) { + if (nodeName_(element) === 'SELECT' && element.multiple) { + var result = []; + forEach(element.options, function (option) { + if (option.selected) { + result.push(option.value || option.text); + } + }); + return result.length === 0 ? null : result; + } + return element.value; + } + element.value = value; + }, + + html: function(element, value) { + if (isUndefined(value)) { + return element.innerHTML; + } + for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { + jqLiteDealoc(childNodes[i]); + } + element.innerHTML = value; + } +}, function(fn, name){ + /** + * Properties: writes return selection, reads return first value + */ + JQLite.prototype[name] = function(arg1, arg2) { + var i, key; + + // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it + // in a way that survives minification. + if (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined) { + if (isObject(arg1)) { + + // we are a write, but the object properties are the key/values + for(i=0; i < this.length; i++) { + if (fn === jqLiteData) { + // data() takes the whole object in jQuery + fn(this[i], arg1); + } else { + for (key in arg1) { + fn(this[i], key, arg1[key]); + } + } + } + // return self for chaining + return this; + } else { + // we are a read, so read the first child. + var value = fn.$dv; + // Only if we have $dv do we iterate over all, otherwise it is just the first element. + var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; + for (var j = 0; j < jj; j++) { + var nodeValue = fn(this[j], arg1, arg2); + value = value ? value + nodeValue : nodeValue; + } + return value; + } + } else { + // we are a write, so apply to all children + for(i=0; i < this.length; i++) { + fn(this[i], arg1, arg2); + } + // return self for chaining + return this; + } + }; +}); + +function createEventHandler(element, events) { + var eventHandler = function (event, type) { + if (!event.preventDefault) { + event.preventDefault = function() { + event.returnValue = false; //ie + }; + } + + if (!event.stopPropagation) { + event.stopPropagation = function() { + event.cancelBubble = true; //ie + }; + } + + if (!event.target) { + event.target = event.srcElement || document; + } + + if (isUndefined(event.defaultPrevented)) { + var prevent = event.preventDefault; + event.preventDefault = function() { + event.defaultPrevented = true; + prevent.call(event); + }; + event.defaultPrevented = false; + } + + event.isDefaultPrevented = function() { + return event.defaultPrevented || event.returnValue === false; + }; + + forEach(events[type || event.type], function(fn) { + fn.call(element, event); + }); + + // Remove monkey-patched methods (IE), + // as they would cause memory leaks in IE8. + if (msie <= 8) { + // IE7/8 does not allow to delete property on native object + event.preventDefault = null; + event.stopPropagation = null; + event.isDefaultPrevented = null; + } else { + // It shouldn't affect normal browsers (native methods are defined on prototype). + delete event.preventDefault; + delete event.stopPropagation; + delete event.isDefaultPrevented; + } + }; + eventHandler.elem = element; + return eventHandler; +} + +////////////////////////////////////////// +// Functions iterating traversal. +// These functions chain results into a single +// selector. +////////////////////////////////////////// +forEach({ + removeData: jqLiteRemoveData, + + dealoc: jqLiteDealoc, + + on: function onFn(element, type, fn, unsupported){ + if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); + + var events = jqLiteExpandoStore(element, 'events'), + handle = jqLiteExpandoStore(element, 'handle'); + + if (!events) jqLiteExpandoStore(element, 'events', events = {}); + if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); + + forEach(type.split(' '), function(type){ + var eventFns = events[type]; + + if (!eventFns) { + if (type == 'mouseenter' || type == 'mouseleave') { + var contains = document.body.contains || document.body.compareDocumentPosition ? + function( a, b ) { + // jshint bitwise: false + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + events[type] = []; + + // Refer to jQuery's implementation of mouseenter & mouseleave + // Read about mouseenter and mouseleave: + // http://www.quirksmode.org/js/events_mouse.html#link8 + var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; + + onFn(element, eventmap[type], function(event) { + var target = this, related = event.relatedTarget; + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !contains(target, related)) ){ + handle(event, type); + } + }); + + } else { + addEventListenerFn(element, type, handle); + events[type] = []; + } + eventFns = events[type]; + } + eventFns.push(fn); + }); + }, + + off: jqLiteOff, + + replaceWith: function(element, replaceNode) { + var index, parent = element.parentNode; + jqLiteDealoc(element); + forEach(new JQLite(replaceNode), function(node){ + if (index) { + parent.insertBefore(node, index.nextSibling); + } else { + parent.replaceChild(node, element); + } + index = node; + }); + }, + + children: function(element) { + var children = []; + forEach(element.childNodes, function(element){ + if (element.nodeType === 1) + children.push(element); + }); + return children; + }, + + contents: function(element) { + return element.childNodes || []; + }, + + append: function(element, node) { + forEach(new JQLite(node), function(child){ + if (element.nodeType === 1 || element.nodeType === 11) { + element.appendChild(child); + } + }); + }, + + prepend: function(element, node) { + if (element.nodeType === 1) { + var index = element.firstChild; + forEach(new JQLite(node), function(child){ + element.insertBefore(child, index); + }); + } + }, + + wrap: function(element, wrapNode) { + wrapNode = jqLite(wrapNode)[0]; + var parent = element.parentNode; + if (parent) { + parent.replaceChild(wrapNode, element); + } + wrapNode.appendChild(element); + }, + + remove: function(element) { + jqLiteDealoc(element); + var parent = element.parentNode; + if (parent) parent.removeChild(element); + }, + + after: function(element, newElement) { + var index = element, parent = element.parentNode; + forEach(new JQLite(newElement), function(node){ + parent.insertBefore(node, index.nextSibling); + index = node; + }); + }, + + addClass: jqLiteAddClass, + removeClass: jqLiteRemoveClass, + + toggleClass: function(element, selector, condition) { + if (isUndefined(condition)) { + condition = !jqLiteHasClass(element, selector); + } + (condition ? jqLiteAddClass : jqLiteRemoveClass)(element, selector); + }, + + parent: function(element) { + var parent = element.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + + next: function(element) { + if (element.nextElementSibling) { + return element.nextElementSibling; + } + + // IE8 doesn't have nextElementSibling + var elm = element.nextSibling; + while (elm != null && elm.nodeType !== 1) { + elm = elm.nextSibling; + } + return elm; + }, + + find: function(element, selector) { + return element.getElementsByTagName(selector); + }, + + clone: jqLiteClone, + + triggerHandler: function(element, eventName, eventData) { + var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; + + eventData = eventData || []; + + var event = [{ + preventDefault: noop, + stopPropagation: noop + }]; + + forEach(eventFns, function(fn) { + fn.apply(element, event.concat(eventData)); + }); + } +}, function(fn, name){ + /** + * chaining functions + */ + JQLite.prototype[name] = function(arg1, arg2, arg3) { + var value; + for(var i=0; i < this.length; i++) { + if (isUndefined(value)) { + value = fn(this[i], arg1, arg2, arg3); + if (isDefined(value)) { + // any function which returns a value needs to be wrapped + value = jqLite(value); + } + } else { + jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); + } + } + return isDefined(value) ? value : this; + }; + + // bind legacy bind/unbind to on/off + JQLite.prototype.bind = JQLite.prototype.on; + JQLite.prototype.unbind = JQLite.prototype.off; +}); + +/** + * Computes a hash of an 'obj'. + * Hash of a: + * string is string + * number is number as string + * object is either result of calling $$hashKey function on the object or uniquely generated id, + * that is also assigned to the $$hashKey property of the object. + * + * @param obj + * @returns {string} hash string such that the same input will have the same hash string. + * The resulting string key is in 'type:hashKey' format. + */ +function hashKey(obj) { + var objType = typeof obj, + key; + + if (objType == 'object' && obj !== null) { + if (typeof (key = obj.$$hashKey) == 'function') { + // must invoke on object to keep the right this + key = obj.$$hashKey(); + } else if (key === undefined) { + key = obj.$$hashKey = nextUid(); + } + } else { + key = obj; + } + + return objType + ':' + key; +} + +/** + * HashMap which can use objects as keys + */ +function HashMap(array){ + forEach(array, this.put, this); +} +HashMap.prototype = { + /** + * Store key value pair + * @param key key to store can be any type + * @param value value to store can be any type + */ + put: function(key, value) { + this[hashKey(key)] = value; + }, + + /** + * @param key + * @returns the value for the key + */ + get: function(key) { + return this[hashKey(key)]; + }, + + /** + * Remove the key/value pair + * @param key + */ + remove: function(key) { + var value = this[key = hashKey(key)]; + delete this[key]; + return value; + } +}; + +/** + * @ngdoc function + * @name angular.injector + * @function + * + * @description + * Creates an injector function that can be used for retrieving services as well as for + * dependency injection (see {@link guide/di dependency injection}). + * + + * @param {Array.} modules A list of module functions or their aliases. See + * {@link angular.module}. The `ng` module must be explicitly added. + * @returns {function()} Injector function. See {@link AUTO.$injector $injector}. + * + * @example + * Typical usage + *
      + *   // create an injector
      + *   var $injector = angular.injector(['ng']);
      + *
      + *   // use the injector to kick off your application
      + *   // use the type inference to auto inject arguments, or use implicit injection
      + *   $injector.invoke(function($rootScope, $compile, $document){
      + *     $compile($document)($rootScope);
      + *     $rootScope.$digest();
      + *   });
      + * 
      + */ + + +/** + * @ngdoc overview + * @name AUTO + * @description + * + * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}. + */ + +var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; +var FN_ARG_SPLIT = /,/; +var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; +var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; +var $injectorMinErr = minErr('$injector'); +function annotate(fn) { + var $inject, + fnText, + argDecl, + last; + + if (typeof fn == 'function') { + if (!($inject = fn.$inject)) { + $inject = []; + if (fn.length) { + fnText = fn.toString().replace(STRIP_COMMENTS, ''); + argDecl = fnText.match(FN_ARGS); + forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ + arg.replace(FN_ARG, function(all, underscore, name){ + $inject.push(name); + }); + }); + } + fn.$inject = $inject; + } + } else if (isArray(fn)) { + last = fn.length - 1; + assertArgFn(fn[last], 'fn'); + $inject = fn.slice(0, last); + } else { + assertArgFn(fn, 'fn', true); + } + return $inject; +} + +/////////////////////////////////////// + +/** + * @ngdoc object + * @name AUTO.$injector + * @function + * + * @description + * + * `$injector` is used to retrieve object instances as defined by + * {@link AUTO.$provide provider}, instantiate types, invoke methods, + * and load modules. + * + * The following always holds true: + * + *
      + *   var $injector = angular.injector();
      + *   expect($injector.get('$injector')).toBe($injector);
      + *   expect($injector.invoke(function($injector){
      + *     return $injector;
      + *   }).toBe($injector);
      + * 
      + * + * # Injection Function Annotation + * + * JavaScript does not have annotations, and annotations are needed for dependency injection. The + * following are all valid ways of annotating function with injection arguments and are equivalent. + * + *
      + *   // inferred (only works if code not minified/obfuscated)
      + *   $injector.invoke(function(serviceA){});
      + *
      + *   // annotated
      + *   function explicit(serviceA) {};
      + *   explicit.$inject = ['serviceA'];
      + *   $injector.invoke(explicit);
      + *
      + *   // inline
      + *   $injector.invoke(['serviceA', function(serviceA){}]);
      + * 
      + * + * ## Inference + * + * In JavaScript calling `toString()` on a function returns the function definition. The definition + * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with + * minification, and obfuscation tools since these tools change the argument names. + * + * ## `$inject` Annotation + * By adding a `$inject` property onto a function the injection parameters can be specified. + * + * ## Inline + * As an array of injection names, where the last item in the array is the function to call. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#get + * @methodOf AUTO.$injector + * + * @description + * Return an instance of the service. + * + * @param {string} name The name of the instance to retrieve. + * @return {*} The instance. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#invoke + * @methodOf AUTO.$injector + * + * @description + * Invoke the method and supply the method arguments from the `$injector`. + * + * @param {!function} fn The function to invoke. Function parameters are injected according to the + * {@link guide/di $inject Annotation} rules. + * @param {Object=} self The `this` for the invoked method. + * @param {Object=} locals Optional object. If preset then any argument names are read from this + * object first, before the `$injector` is consulted. + * @returns {*} the value returned by the invoked `fn` function. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#has + * @methodOf AUTO.$injector + * + * @description + * Allows the user to query if the particular service exist. + * + * @param {string} Name of the service to query. + * @returns {boolean} returns true if injector has given service. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#instantiate + * @methodOf AUTO.$injector + * @description + * Create a new instance of JS type. The method takes a constructor function invokes the new + * operator and supplies all of the arguments to the constructor function as specified by the + * constructor annotation. + * + * @param {function} Type Annotated constructor function. + * @param {Object=} locals Optional object. If preset then any argument names are read from this + * object first, before the `$injector` is consulted. + * @returns {Object} new instance of `Type`. + */ + +/** + * @ngdoc method + * @name AUTO.$injector#annotate + * @methodOf AUTO.$injector + * + * @description + * Returns an array of service names which the function is requesting for injection. This API is + * used by the injector to determine which services need to be injected into the function when the + * function is invoked. There are three ways in which the function can be annotated with the needed + * dependencies. + * + * # Argument names + * + * The simplest form is to extract the dependencies from the arguments of the function. This is done + * by converting the function into a string using `toString()` method and extracting the argument + * names. + *
      + *   // Given
      + *   function MyController($scope, $route) {
      + *     // ...
      + *   }
      + *
      + *   // Then
      + *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
      + * 
      + * + * This method does not work with code minification / obfuscation. For this reason the following + * annotation strategies are supported. + * + * # The `$inject` property + * + * If a function has an `$inject` property and its value is an array of strings, then the strings + * represent names of services to be injected into the function. + *
      + *   // Given
      + *   var MyController = function(obfuscatedScope, obfuscatedRoute) {
      + *     // ...
      + *   }
      + *   // Define function dependencies
      + *   MyController.$inject = ['$scope', '$route'];
      + *
      + *   // Then
      + *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
      + * 
      + * + * # The array notation + * + * It is often desirable to inline Injected functions and that's when setting the `$inject` property + * is very inconvenient. In these situations using the array notation to specify the dependencies in + * a way that survives minification is a better choice: + * + *
      + *   // We wish to write this (not minification / obfuscation safe)
      + *   injector.invoke(function($compile, $rootScope) {
      + *     // ...
      + *   });
      + *
      + *   // We are forced to write break inlining
      + *   var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
      + *     // ...
      + *   };
      + *   tmpFn.$inject = ['$compile', '$rootScope'];
      + *   injector.invoke(tmpFn);
      + *
      + *   // To better support inline function the inline annotation is supported
      + *   injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
      + *     // ...
      + *   }]);
      + *
      + *   // Therefore
      + *   expect(injector.annotate(
      + *      ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
      + *    ).toEqual(['$compile', '$rootScope']);
      + * 
      + * + * @param {function|Array.} fn Function for which dependent service names need to + * be retrieved as described above. + * + * @returns {Array.} The names of the services which the function requires. + */ + + + + +/** + * @ngdoc object + * @name AUTO.$provide + * + * @description + * + * The {@link AUTO.$provide $provide} service has a number of methods for registering components + * with the {@link AUTO.$injector $injector}. Many of these functions are also exposed on + * {@link angular.Module}. + * + * An Angular **service** is a singleton object created by a **service factory**. These **service + * factories** are functions which, in turn, are created by a **service provider**. + * The **service providers** are constructor functions. When instantiated they must contain a + * property called `$get`, which holds the **service factory** function. + * + * When you request a service, the {@link AUTO.$injector $injector} is responsible for finding the + * correct **service provider**, instantiating it and then calling its `$get` **service factory** + * function to get the instance of the **service**. + * + * Often services have no configuration options and there is no need to add methods to the service + * provider. The provider will be no more than a constructor function with a `$get` property. For + * these cases the {@link AUTO.$provide $provide} service has additional helper methods to register + * services without specifying a provider. + * + * * {@link AUTO.$provide#methods_provider provider(provider)} - registers a **service provider** with the + * {@link AUTO.$injector $injector} + * * {@link AUTO.$provide#methods_constant constant(obj)} - registers a value/object that can be accessed by + * providers and services. + * * {@link AUTO.$provide#methods_value value(obj)} - registers a value/object that can only be accessed by + * services, not providers. + * * {@link AUTO.$provide#methods_factory factory(fn)} - registers a service **factory function**, `fn`, + * that will be wrapped in a **service provider** object, whose `$get` property will contain the + * given factory function. + * * {@link AUTO.$provide#methods_service service(class)} - registers a **constructor function**, `class` that + * that will be wrapped in a **service provider** object, whose `$get` property will instantiate + * a new object using the given constructor function. + * + * See the individual methods for more information and examples. + */ + +/** + * @ngdoc method + * @name AUTO.$provide#provider + * @methodOf AUTO.$provide + * @description + * + * Register a **provider function** with the {@link AUTO.$injector $injector}. Provider functions + * are constructor functions, whose instances are responsible for "providing" a factory for a + * service. + * + * Service provider names start with the name of the service they provide followed by `Provider`. + * For example, the {@link ng.$log $log} service has a provider called + * {@link ng.$logProvider $logProvider}. + * + * Service provider objects can have additional methods which allow configuration of the provider + * and its service. Importantly, you can configure what kind of service is created by the `$get` + * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a + * method {@link ng.$logProvider#debugEnabled debugEnabled} + * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the + * console or not. + * + * @param {string} name The name of the instance. NOTE: the provider will be available under `name + + 'Provider'` key. + * @param {(Object|function())} provider If the provider is: + * + * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using + * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be + * created. + * - `Constructor`: a new instance of the provider will be created using + * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as + * `object`. + * + * @returns {Object} registered provider instance + + * @example + * + * The following example shows how to create a simple event tracking service and register it using + * {@link AUTO.$provide#methods_provider $provide.provider()}. + * + *
      + *  // Define the eventTracker provider
      + *  function EventTrackerProvider() {
      + *    var trackingUrl = '/track';
      + *
      + *    // A provider method for configuring where the tracked events should been saved
      + *    this.setTrackingUrl = function(url) {
      + *      trackingUrl = url;
      + *    };
      + *
      + *    // The service factory function
      + *    this.$get = ['$http', function($http) {
      + *      var trackedEvents = {};
      + *      return {
      + *        // Call this to track an event
      + *        event: function(event) {
      + *          var count = trackedEvents[event] || 0;
      + *          count += 1;
      + *          trackedEvents[event] = count;
      + *          return count;
      + *        },
      + *        // Call this to save the tracked events to the trackingUrl
      + *        save: function() {
      + *          $http.post(trackingUrl, trackedEvents);
      + *        }
      + *      };
      + *    }];
      + *  }
      + *
      + *  describe('eventTracker', function() {
      + *    var postSpy;
      + *
      + *    beforeEach(module(function($provide) {
      + *      // Register the eventTracker provider
      + *      $provide.provider('eventTracker', EventTrackerProvider);
      + *    }));
      + *
      + *    beforeEach(module(function(eventTrackerProvider) {
      + *      // Configure eventTracker provider
      + *      eventTrackerProvider.setTrackingUrl('/custom-track');
      + *    }));
      + *
      + *    it('tracks events', inject(function(eventTracker) {
      + *      expect(eventTracker.event('login')).toEqual(1);
      + *      expect(eventTracker.event('login')).toEqual(2);
      + *    }));
      + *
      + *    it('saves to the tracking url', inject(function(eventTracker, $http) {
      + *      postSpy = spyOn($http, 'post');
      + *      eventTracker.event('login');
      + *      eventTracker.save();
      + *      expect(postSpy).toHaveBeenCalled();
      + *      expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
      + *      expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
      + *      expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
      + *    }));
      + *  });
      + * 
      + */ + +/** + * @ngdoc method + * @name AUTO.$provide#factory + * @methodOf AUTO.$provide + * @description + * + * Register a **service factory**, which will be called to return the service instance. + * This is short for registering a service where its provider consists of only a `$get` property, + * which is the given service factory function. + * You should use {@link AUTO.$provide#factory $provide.factory(getFn)} if you do not need to + * configure your service in a provider. + * + * @param {string} name The name of the instance. + * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand + * for `$provide.provider(name, {$get: $getFn})`. + * @returns {Object} registered provider instance + * + * @example + * Here is an example of registering a service + *
      + *   $provide.factory('ping', ['$http', function($http) {
      + *     return function ping() {
      + *       return $http.send('/ping');
      + *     };
      + *   }]);
      + * 
      + * You would then inject and use this service like this: + *
      + *   someModule.controller('Ctrl', ['ping', function(ping) {
      + *     ping();
      + *   }]);
      + * 
      + */ + + +/** + * @ngdoc method + * @name AUTO.$provide#service + * @methodOf AUTO.$provide + * @description + * + * Register a **service constructor**, which will be invoked with `new` to create the service + * instance. + * This is short for registering a service where its provider's `$get` property is the service + * constructor function that will be used to instantiate the service instance. + * + * You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service + * as a type/class. This is common when using {@link http://coffeescript.org CoffeeScript}. + * + * @param {string} name The name of the instance. + * @param {Function} constructor A class (constructor function) that will be instantiated. + * @returns {Object} registered provider instance + * + * @example + * Here is an example of registering a service using + * {@link AUTO.$provide#methods_service $provide.service(class)} that is defined as a CoffeeScript class. + *
      + *   class Ping
      + *     constructor: (@$http)->
      + *     send: ()=>
      + *       @$http.get('/ping')
      + *
      + *   $provide.service('ping', ['$http', Ping])
      + * 
      + * You would then inject and use this service like this: + *
      + *   someModule.controller 'Ctrl', ['ping', (ping)->
      + *     ping.send()
      + *   ]
      + * 
      + */ + + +/** + * @ngdoc method + * @name AUTO.$provide#value + * @methodOf AUTO.$provide + * @description + * + * Register a **value service** with the {@link AUTO.$injector $injector}, such as a string, a + * number, an array, an object or a function. This is short for registering a service where its + * provider's `$get` property is a factory function that takes no arguments and returns the **value + * service**. + * + * Value services are similar to constant services, except that they cannot be injected into a + * module configuration function (see {@link angular.Module#config}) but they can be overridden by + * an Angular + * {@link AUTO.$provide#decorator decorator}. + * + * @param {string} name The name of the instance. + * @param {*} value The value. + * @returns {Object} registered provider instance + * + * @example + * Here are some examples of creating value services. + *
      + *   $provide.constant('ADMIN_USER', 'admin');
      + *
      + *   $provide.constant('RoleLookup', { admin: 0, writer: 1, reader: 2 });
      + *
      + *   $provide.constant('halfOf', function(value) {
      + *     return value / 2;
      + *   });
      + * 
      + */ + + +/** + * @ngdoc method + * @name AUTO.$provide#constant + * @methodOf AUTO.$provide + * @description + * + * Register a **constant service**, such as a string, a number, an array, an object or a function, + * with the {@link AUTO.$injector $injector}. Unlike {@link AUTO.$provide#value value} it can be + * injected into a module configuration function (see {@link angular.Module#config}) and it cannot + * be overridden by an Angular {@link AUTO.$provide#decorator decorator}. + * + * @param {string} name The name of the constant. + * @param {*} value The constant value. + * @returns {Object} registered instance + * + * @example + * Here a some examples of creating constants: + *
      + *   $provide.constant('SHARD_HEIGHT', 306);
      + *
      + *   $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
      + *
      + *   $provide.constant('double', function(value) {
      + *     return value * 2;
      + *   });
      + * 
      + */ + + +/** + * @ngdoc method + * @name AUTO.$provide#decorator + * @methodOf AUTO.$provide + * @description + * + * Register a **service decorator** with the {@link AUTO.$injector $injector}. A service decorator + * intercepts the creation of a service, allowing it to override or modify the behaviour of the + * service. The object returned by the decorator may be the original service, or a new service + * object which replaces or wraps and delegates to the original service. + * + * @param {string} name The name of the service to decorate. + * @param {function()} decorator This function will be invoked when the service needs to be + * instantiated and should return the decorated service instance. The function is called using + * the {@link AUTO.$injector#invoke injector.invoke} method and is therefore fully injectable. + * Local injection arguments: + * + * * `$delegate` - The original service instance, which can be monkey patched, configured, + * decorated or delegated to. + * + * @example + * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting + * calls to {@link ng.$log#error $log.warn()}. + *
      + *   $provider.decorator('$log', ['$delegate', function($delegate) {
      + *     $delegate.warn = $delegate.error;
      + *     return $delegate;
      + *   }]);
      + * 
      + */ + + +function createInjector(modulesToLoad) { + var INSTANTIATING = {}, + providerSuffix = 'Provider', + path = [], + loadedModules = new HashMap(), + providerCache = { + $provide: { + provider: supportObject(provider), + factory: supportObject(factory), + service: supportObject(service), + value: supportObject(value), + constant: supportObject(constant), + decorator: decorator + } + }, + providerInjector = (providerCache.$injector = + createInternalInjector(providerCache, function() { + throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); + })), + instanceCache = {}, + instanceInjector = (instanceCache.$injector = + createInternalInjector(instanceCache, function(servicename) { + var provider = providerInjector.get(servicename + providerSuffix); + return instanceInjector.invoke(provider.$get, provider); + })); + + + forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); + + return instanceInjector; + + //////////////////////////////////// + // $provider + //////////////////////////////////// + + function supportObject(delegate) { + return function(key, value) { + if (isObject(key)) { + forEach(key, reverseParams(delegate)); + } else { + return delegate(key, value); + } + }; + } + + function provider(name, provider_) { + assertNotHasOwnProperty(name, 'service'); + if (isFunction(provider_) || isArray(provider_)) { + provider_ = providerInjector.instantiate(provider_); + } + if (!provider_.$get) { + throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); + } + return providerCache[name + providerSuffix] = provider_; + } + + function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } + + function service(name, constructor) { + return factory(name, ['$injector', function($injector) { + return $injector.instantiate(constructor); + }]); + } + + function value(name, val) { return factory(name, valueFn(val)); } + + function constant(name, value) { + assertNotHasOwnProperty(name, 'constant'); + providerCache[name] = value; + instanceCache[name] = value; + } + + function decorator(serviceName, decorFn) { + var origProvider = providerInjector.get(serviceName + providerSuffix), + orig$get = origProvider.$get; + + origProvider.$get = function() { + var origInstance = instanceInjector.invoke(orig$get, origProvider); + return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); + }; + } + + //////////////////////////////////// + // Module Loading + //////////////////////////////////// + function loadModules(modulesToLoad){ + var runBlocks = [], moduleFn, invokeQueue, i, ii; + forEach(modulesToLoad, function(module) { + if (loadedModules.get(module)) return; + loadedModules.put(module, true); + + try { + if (isString(module)) { + moduleFn = angularModule(module); + runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); + + for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { + var invokeArgs = invokeQueue[i], + provider = providerInjector.get(invokeArgs[0]); + + provider[invokeArgs[1]].apply(provider, invokeArgs[2]); + } + } else if (isFunction(module)) { + runBlocks.push(providerInjector.invoke(module)); + } else if (isArray(module)) { + runBlocks.push(providerInjector.invoke(module)); + } else { + assertArgFn(module, 'module'); + } + } catch (e) { + if (isArray(module)) { + module = module[module.length - 1]; + } + if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { + // Safari & FF's stack traces don't contain error.message content + // unlike those of Chrome and IE + // So if stack doesn't contain message, we create a new string that contains both. + // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. + /* jshint -W022 */ + e = e.message + '\n' + e.stack; + } + throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", + module, e.stack || e.message || e); + } + }); + return runBlocks; + } + + //////////////////////////////////// + // internal Injector + //////////////////////////////////// + + function createInternalInjector(cache, factory) { + + function getService(serviceName) { + if (cache.hasOwnProperty(serviceName)) { + if (cache[serviceName] === INSTANTIATING) { + throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); + } + return cache[serviceName]; + } else { + try { + path.unshift(serviceName); + cache[serviceName] = INSTANTIATING; + return cache[serviceName] = factory(serviceName); + } finally { + path.shift(); + } + } + } + + function invoke(fn, self, locals){ + var args = [], + $inject = annotate(fn), + length, i, + key; + + for(i = 0, length = $inject.length; i < length; i++) { + key = $inject[i]; + if (typeof key !== 'string') { + throw $injectorMinErr('itkn', + 'Incorrect injection token! Expected service name as string, got {0}', key); + } + args.push( + locals && locals.hasOwnProperty(key) + ? locals[key] + : getService(key) + ); + } + if (!fn.$inject) { + // this means that we must be an array. + fn = fn[length]; + } + + + // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke + switch (self ? -1 : args.length) { + case 0: return fn(); + case 1: return fn(args[0]); + case 2: return fn(args[0], args[1]); + case 3: return fn(args[0], args[1], args[2]); + case 4: return fn(args[0], args[1], args[2], args[3]); + case 5: return fn(args[0], args[1], args[2], args[3], args[4]); + case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]); + case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); + case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); + case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], + args[8]); + case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], + args[8], args[9]); + default: return fn.apply(self, args); + } + } + + function instantiate(Type, locals) { + var Constructor = function() {}, + instance, returnedValue; + + // Check if Type is annotated and use just the given function at n-1 as parameter + // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); + Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; + instance = new Constructor(); + returnedValue = invoke(Type, instance, locals); + + return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; + } + + return { + invoke: invoke, + instantiate: instantiate, + get: getService, + annotate: annotate, + has: function(name) { + return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); + } + }; + } +} + +/** + * @ngdoc function + * @name ng.$anchorScroll + * @requires $window + * @requires $location + * @requires $rootScope + * + * @description + * When called, it checks current value of `$location.hash()` and scroll to related element, + * according to rules specified in + * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}. + * + * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. + * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. + * + * @example + + +
      + Go to bottom + You're at the bottom! +
      +
      + + function ScrollCtrl($scope, $location, $anchorScroll) { + $scope.gotoBottom = function (){ + // set the location.hash to the id of + // the element you wish to scroll to. + $location.hash('bottom'); + + // call $anchorScroll() + $anchorScroll(); + } + } + + + #scrollArea { + height: 350px; + overflow: auto; + } + + #bottom { + display: block; + margin-top: 2000px; + } + +
      + */ +function $AnchorScrollProvider() { + + var autoScrollingEnabled = true; + + this.disableAutoScrolling = function() { + autoScrollingEnabled = false; + }; + + this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { + var document = $window.document; + + // helper function to get first anchor from a NodeList + // can't use filter.filter, as it accepts only instances of Array + // and IE can't convert NodeList to an array using [].slice + // TODO(vojta): use filter if we change it to accept lists as well + function getFirstAnchor(list) { + var result = null; + forEach(list, function(element) { + if (!result && lowercase(element.nodeName) === 'a') result = element; + }); + return result; + } + + function scroll() { + var hash = $location.hash(), elm; + + // empty hash, scroll to the top of the page + if (!hash) $window.scrollTo(0, 0); + + // element with given id + else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); + + // first anchor with given name :-D + else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); + + // no element and hash == 'top', scroll to the top of the page + else if (hash === 'top') $window.scrollTo(0, 0); + } + + // does not scroll when user clicks on anchor link that is currently on + // (no url change, no $location.hash() change), browser native does scroll + if (autoScrollingEnabled) { + $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, + function autoScrollWatchAction() { + $rootScope.$evalAsync(scroll); + }); + } + + return scroll; + }]; +} + +var $animateMinErr = minErr('$animate'); + +/** + * @ngdoc object + * @name ng.$animateProvider + * + * @description + * Default implementation of $animate that doesn't perform any animations, instead just + * synchronously performs DOM + * updates and calls done() callbacks. + * + * In order to enable animations the ngAnimate module has to be loaded. + * + * To see the functional implementation check out src/ngAnimate/animate.js + */ +var $AnimateProvider = ['$provide', function($provide) { + + + this.$$selectors = {}; + + + /** + * @ngdoc function + * @name ng.$animateProvider#register + * @methodOf ng.$animateProvider + * + * @description + * Registers a new injectable animation factory function. The factory function produces the + * animation object which contains callback functions for each event that is expected to be + * animated. + * + * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` + * must be called once the element animation is complete. If a function is returned then the + * animation service will use this function to cancel the animation whenever a cancel event is + * triggered. + * + * + *
      +   *   return {
      +     *     eventFn : function(element, done) {
      +     *       //code to run the animation
      +     *       //once complete, then run done()
      +     *       return function cancellationFunction() {
      +     *         //code to cancel the animation
      +     *       }
      +     *     }
      +     *   }
      +   *
      + * + * @param {string} name The name of the animation. + * @param {function} factory The factory function that will be executed to return the animation + * object. + */ + this.register = function(name, factory) { + var key = name + '-animation'; + if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', + "Expecting class selector starting with '.' got '{0}'.", name); + this.$$selectors[name.substr(1)] = key; + $provide.factory(key, factory); + }; + + this.$get = ['$timeout', function($timeout) { + + /** + * + * @ngdoc object + * @name ng.$animate + * @description The $animate service provides rudimentary DOM manipulation functions to + * insert, remove and move elements within the DOM, as well as adding and removing classes. + * This service is the core service used by the ngAnimate $animator service which provides + * high-level animation hooks for CSS and JavaScript. + * + * $animate is available in the AngularJS core, however, the ngAnimate module must be included + * to enable full out animation support. Otherwise, $animate will only perform simple DOM + * manipulation operations. + * + * To learn more about enabling animation support, click here to visit the {@link ngAnimate + * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service + * page}. + */ + return { + + /** + * + * @ngdoc function + * @name ng.$animate#enter + * @methodOf ng.$animate + * @function + * @description Inserts the element into the DOM either after the `after` element or within + * the `parent` element. Once complete, the done() callback will be fired (if provided). + * @param {jQuery/jqLite element} element the element which will be inserted into the DOM + * @param {jQuery/jqLite element} parent the parent element which will append the element as + * a child (if the after element is not present) + * @param {jQuery/jqLite element} after the sibling element which will append the element + * after itself + * @param {function=} done callback function that will be called after the element has been + * inserted into the DOM + */ + enter : function(element, parent, after, done) { + var afterNode = after && after[after.length - 1]; + var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; + // IE does not like undefined so we have to pass null. + var afterNextSibling = (afterNode && afterNode.nextSibling) || null; + forEach(element, function(node) { + parentNode.insertBefore(node, afterNextSibling); + }); + done && $timeout(done, 0, false); + }, + + /** + * + * @ngdoc function + * @name ng.$animate#leave + * @methodOf ng.$animate + * @function + * @description Removes the element from the DOM. Once complete, the done() callback will be + * fired (if provided). + * @param {jQuery/jqLite element} element the element which will be removed from the DOM + * @param {function=} done callback function that will be called after the element has been + * removed from the DOM + */ + leave : function(element, done) { + element.remove(); + done && $timeout(done, 0, false); + }, + + /** + * + * @ngdoc function + * @name ng.$animate#move + * @methodOf ng.$animate + * @function + * @description Moves the position of the provided element within the DOM to be placed + * either after the `after` element or inside of the `parent` element. Once complete, the + * done() callback will be fired (if provided). + * + * @param {jQuery/jqLite element} element the element which will be moved around within the + * DOM + * @param {jQuery/jqLite element} parent the parent element where the element will be + * inserted into (if the after element is not present) + * @param {jQuery/jqLite element} after the sibling element where the element will be + * positioned next to + * @param {function=} done the callback function (if provided) that will be fired after the + * element has been moved to its new position + */ + move : function(element, parent, after, done) { + // Do not remove element before insert. Removing will cause data associated with the + // element to be dropped. Insert will implicitly do the remove. + this.enter(element, parent, after, done); + }, + + /** + * + * @ngdoc function + * @name ng.$animate#addClass + * @methodOf ng.$animate + * @function + * @description Adds the provided className CSS class value to the provided element. Once + * complete, the done() callback will be fired (if provided). + * @param {jQuery/jqLite element} element the element which will have the className value + * added to it + * @param {string} className the CSS class which will be added to the element + * @param {function=} done the callback function (if provided) that will be fired after the + * className value has been added to the element + */ + addClass : function(element, className, done) { + className = isString(className) ? + className : + isArray(className) ? className.join(' ') : ''; + forEach(element, function (element) { + jqLiteAddClass(element, className); + }); + done && $timeout(done, 0, false); + }, + + /** + * + * @ngdoc function + * @name ng.$animate#removeClass + * @methodOf ng.$animate + * @function + * @description Removes the provided className CSS class value from the provided element. + * Once complete, the done() callback will be fired (if provided). + * @param {jQuery/jqLite element} element the element which will have the className value + * removed from it + * @param {string} className the CSS class which will be removed from the element + * @param {function=} done the callback function (if provided) that will be fired after the + * className value has been removed from the element + */ + removeClass : function(element, className, done) { + className = isString(className) ? + className : + isArray(className) ? className.join(' ') : ''; + forEach(element, function (element) { + jqLiteRemoveClass(element, className); + }); + done && $timeout(done, 0, false); + }, + + enabled : noop + }; + }]; +}]; + +/** + * ! This is a private undocumented service ! + * + * @name ng.$browser + * @requires $log + * @description + * This object has two goals: + * + * - hide all the global state in the browser caused by the window object + * - abstract away all the browser specific features and inconsistencies + * + * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` + * service, which can be used for convenient testing of the application without the interaction with + * the real browser apis. + */ +/** + * @param {object} window The global window object. + * @param {object} document jQuery wrapped document. + * @param {function()} XHR XMLHttpRequest constructor. + * @param {object} $log console.log or an object with the same interface. + * @param {object} $sniffer $sniffer service + */ +function Browser(window, document, $log, $sniffer) { + var self = this, + rawDocument = document[0], + location = window.location, + history = window.history, + setTimeout = window.setTimeout, + clearTimeout = window.clearTimeout, + pendingDeferIds = {}; + + self.isMock = false; + + var outstandingRequestCount = 0; + var outstandingRequestCallbacks = []; + + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = completeOutstandingRequest; + self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; + + /** + * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` + * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. + */ + function completeOutstandingRequest(fn) { + try { + fn.apply(null, sliceArgs(arguments, 1)); + } finally { + outstandingRequestCount--; + if (outstandingRequestCount === 0) { + while(outstandingRequestCallbacks.length) { + try { + outstandingRequestCallbacks.pop()(); + } catch (e) { + $log.error(e); + } + } + } + } + } + + /** + * @private + * Note: this method is used only by scenario runner + * TODO(vojta): prefix this method with $$ ? + * @param {function()} callback Function that will be called when no outstanding request + */ + self.notifyWhenNoOutstandingRequests = function(callback) { + // force browser to execute all pollFns - this is needed so that cookies and other pollers fire + // at some deterministic time in respect to the test runner's actions. Leaving things up to the + // regular poller would result in flaky tests. + forEach(pollFns, function(pollFn){ pollFn(); }); + + if (outstandingRequestCount === 0) { + callback(); + } else { + outstandingRequestCallbacks.push(callback); + } + }; + + ////////////////////////////////////////////////////////////// + // Poll Watcher API + ////////////////////////////////////////////////////////////// + var pollFns = [], + pollTimeout; + + /** + * @name ng.$browser#addPollFn + * @methodOf ng.$browser + * + * @param {function()} fn Poll function to add + * + * @description + * Adds a function to the list of functions that poller periodically executes, + * and starts polling if not started yet. + * + * @returns {function()} the added function + */ + self.addPollFn = function(fn) { + if (isUndefined(pollTimeout)) startPoller(100, setTimeout); + pollFns.push(fn); + return fn; + }; + + /** + * @param {number} interval How often should browser call poll functions (ms) + * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. + * + * @description + * Configures the poller to run in the specified intervals, using the specified + * setTimeout fn and kicks it off. + */ + function startPoller(interval, setTimeout) { + (function check() { + forEach(pollFns, function(pollFn){ pollFn(); }); + pollTimeout = setTimeout(check, interval); + })(); + } + + ////////////////////////////////////////////////////////////// + // URL API + ////////////////////////////////////////////////////////////// + + var lastBrowserUrl = location.href, + baseElement = document.find('base'), + newLocation = null; + + /** + * @name ng.$browser#url + * @methodOf ng.$browser + * + * @description + * GETTER: + * Without any argument, this method just returns current value of location.href. + * + * SETTER: + * With at least one argument, this method sets url to new value. + * If html5 history api supported, pushState/replaceState is used, otherwise + * location.href/location.replace is used. + * Returns its own instance to allow chaining + * + * NOTE: this api is intended for use only by the $location service. Please use the + * {@link ng.$location $location service} to change url. + * + * @param {string} url New url (when used as setter) + * @param {boolean=} replace Should new url replace current history record ? + */ + self.url = function(url, replace) { + // Android Browser BFCache causes location reference to become stale. + if (location !== window.location) location = window.location; + + // setter + if (url) { + if (lastBrowserUrl == url) return; + lastBrowserUrl = url; + if ($sniffer.history) { + if (replace) history.replaceState(null, '', url); + else { + history.pushState(null, '', url); + // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 + baseElement.attr('href', baseElement.attr('href')); + } + } else { + newLocation = url; + if (replace) { + location.replace(url); + } else { + location.href = url; + } + } + return self; + // getter + } else { + // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href + // methods not updating location.href synchronously. + // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 + return newLocation || location.href.replace(/%27/g,"'"); + } + }; + + var urlChangeListeners = [], + urlChangeInit = false; + + function fireUrlChange() { + newLocation = null; + if (lastBrowserUrl == self.url()) return; + + lastBrowserUrl = self.url(); + forEach(urlChangeListeners, function(listener) { + listener(self.url()); + }); + } + + /** + * @name ng.$browser#onUrlChange + * @methodOf ng.$browser + * @TODO(vojta): refactor to use node's syntax for events + * + * @description + * Register callback function that will be called, when url changes. + * + * It's only called when the url is changed by outside of angular: + * - user types different url into address bar + * - user clicks on history (forward/back) button + * - user clicks on a link + * + * It's not called when url is changed by $browser.url() method + * + * The listener gets called with new url as parameter. + * + * NOTE: this api is intended for use only by the $location service. Please use the + * {@link ng.$location $location service} to monitor url changes in angular apps. + * + * @param {function(string)} listener Listener function to be called when url changes. + * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. + */ + self.onUrlChange = function(callback) { + if (!urlChangeInit) { + // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) + // don't fire popstate when user change the address bar and don't fire hashchange when url + // changed by push/replaceState + + // html5 history api - popstate event + if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); + // hashchange event + if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); + // polling + else self.addPollFn(fireUrlChange); + + urlChangeInit = true; + } + + urlChangeListeners.push(callback); + return callback; + }; + + ////////////////////////////////////////////////////////////// + // Misc API + ////////////////////////////////////////////////////////////// + + /** + * @name ng.$browser#baseHref + * @methodOf ng.$browser + * + * @description + * Returns current + * (always relative - without domain) + * + * @returns {string=} current + */ + self.baseHref = function() { + var href = baseElement.attr('href'); + return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : ''; + }; + + ////////////////////////////////////////////////////////////// + // Cookies API + ////////////////////////////////////////////////////////////// + var lastCookies = {}; + var lastCookieString = ''; + var cookiePath = self.baseHref(); + + /** + * @name ng.$browser#cookies + * @methodOf ng.$browser + * + * @param {string=} name Cookie name + * @param {string=} value Cookie value + * + * @description + * The cookies method provides a 'private' low level access to browser cookies. + * It is not meant to be used directly, use the $cookie service instead. + * + * The return values vary depending on the arguments that the method was called with as follows: + * + * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify + * it + * - cookies(name, value) -> set name to value, if value is undefined delete the cookie + * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that + * way) + * + * @returns {Object} Hash of all cookies (if called without any parameter) + */ + self.cookies = function(name, value) { + /* global escape: false, unescape: false */ + var cookieLength, cookieArray, cookie, i, index; + + if (name) { + if (value === undefined) { + rawDocument.cookie = escape(name) + "=;path=" + cookiePath + + ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; + } else { + if (isString(value)) { + cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + + ';path=' + cookiePath).length + 1; + + // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: + // - 300 cookies + // - 20 cookies per unique domain + // - 4096 bytes per cookie + if (cookieLength > 4096) { + $log.warn("Cookie '"+ name + + "' possibly not set or overflowed because it was too large ("+ + cookieLength + " > 4096 bytes)!"); + } + } + } + } else { + if (rawDocument.cookie !== lastCookieString) { + lastCookieString = rawDocument.cookie; + cookieArray = lastCookieString.split("; "); + lastCookies = {}; + + for (i = 0; i < cookieArray.length; i++) { + cookie = cookieArray[i]; + index = cookie.indexOf('='); + if (index > 0) { //ignore nameless cookies + name = unescape(cookie.substring(0, index)); + // the first value that is seen for a cookie is the most + // specific one. values for the same cookie name that + // follow are for less specific paths. + if (lastCookies[name] === undefined) { + lastCookies[name] = unescape(cookie.substring(index + 1)); + } + } + } + } + return lastCookies; + } + }; + + + /** + * @name ng.$browser#defer + * @methodOf ng.$browser + * @param {function()} fn A function, who's execution should be deferred. + * @param {number=} [delay=0] of milliseconds to defer the function execution. + * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. + * + * @description + * Executes a fn asynchronously via `setTimeout(fn, delay)`. + * + * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using + * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed + * via `$browser.defer.flush()`. + * + */ + self.defer = function(fn, delay) { + var timeoutId; + outstandingRequestCount++; + timeoutId = setTimeout(function() { + delete pendingDeferIds[timeoutId]; + completeOutstandingRequest(fn); + }, delay || 0); + pendingDeferIds[timeoutId] = true; + return timeoutId; + }; + + + /** + * @name ng.$browser#defer.cancel + * @methodOf ng.$browser.defer + * + * @description + * Cancels a deferred task identified with `deferId`. + * + * @param {*} deferId Token returned by the `$browser.defer` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + self.defer.cancel = function(deferId) { + if (pendingDeferIds[deferId]) { + delete pendingDeferIds[deferId]; + clearTimeout(deferId); + completeOutstandingRequest(noop); + return true; + } + return false; + }; + +} + +function $BrowserProvider(){ + this.$get = ['$window', '$log', '$sniffer', '$document', + function( $window, $log, $sniffer, $document){ + return new Browser($window, $document, $log, $sniffer); + }]; +} + +/** + * @ngdoc object + * @name ng.$cacheFactory + * + * @description + * Factory that constructs cache objects and gives access to them. + * + *
      + * 
      + *  var cache = $cacheFactory('cacheId');
      + *  expect($cacheFactory.get('cacheId')).toBe(cache);
      + *  expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
      + *
      + *  cache.put("key", "value");
      + *  cache.put("another key", "another value");
      + *
      + *  // We've specified no options on creation
      + *  expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 
      + * 
      + * 
      + * + * + * @param {string} cacheId Name or id of the newly created cache. + * @param {object=} options Options object that specifies the cache behavior. Properties: + * + * - `{number=}` `capacity` — turns the cache into LRU cache. + * + * @returns {object} Newly created cache object with the following set of methods: + * + * - `{object}` `info()` — Returns id, size, and options of cache. + * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns + * it. + * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. + * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. + * - `{void}` `removeAll()` — Removes all cached values. + * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. + * + */ +function $CacheFactoryProvider() { + + this.$get = function() { + var caches = {}; + + function cacheFactory(cacheId, options) { + if (cacheId in caches) { + throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); + } + + var size = 0, + stats = extend({}, options, {id: cacheId}), + data = {}, + capacity = (options && options.capacity) || Number.MAX_VALUE, + lruHash = {}, + freshEnd = null, + staleEnd = null; + + return caches[cacheId] = { + + put: function(key, value) { + var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); + + refresh(lruEntry); + + if (isUndefined(value)) return; + if (!(key in data)) size++; + data[key] = value; + + if (size > capacity) { + this.remove(staleEnd.key); + } + + return value; + }, + + + get: function(key) { + var lruEntry = lruHash[key]; + + if (!lruEntry) return; + + refresh(lruEntry); + + return data[key]; + }, + + + remove: function(key) { + var lruEntry = lruHash[key]; + + if (!lruEntry) return; + + if (lruEntry == freshEnd) freshEnd = lruEntry.p; + if (lruEntry == staleEnd) staleEnd = lruEntry.n; + link(lruEntry.n,lruEntry.p); + + delete lruHash[key]; + delete data[key]; + size--; + }, + + + removeAll: function() { + data = {}; + size = 0; + lruHash = {}; + freshEnd = staleEnd = null; + }, + + + destroy: function() { + data = null; + stats = null; + lruHash = null; + delete caches[cacheId]; + }, + + + info: function() { + return extend({}, stats, {size: size}); + } + }; + + + /** + * makes the `entry` the freshEnd of the LRU linked list + */ + function refresh(entry) { + if (entry != freshEnd) { + if (!staleEnd) { + staleEnd = entry; + } else if (staleEnd == entry) { + staleEnd = entry.n; + } + + link(entry.n, entry.p); + link(entry, freshEnd); + freshEnd = entry; + freshEnd.n = null; + } + } + + + /** + * bidirectionally links two entries of the LRU linked list + */ + function link(nextEntry, prevEntry) { + if (nextEntry != prevEntry) { + if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify + if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify + } + } + } + + + /** + * @ngdoc method + * @name ng.$cacheFactory#info + * @methodOf ng.$cacheFactory + * + * @description + * Get information about all the of the caches that have been created + * + * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` + */ + cacheFactory.info = function() { + var info = {}; + forEach(caches, function(cache, cacheId) { + info[cacheId] = cache.info(); + }); + return info; + }; + + + /** + * @ngdoc method + * @name ng.$cacheFactory#get + * @methodOf ng.$cacheFactory + * + * @description + * Get access to a cache object by the `cacheId` used when it was created. + * + * @param {string} cacheId Name or id of a cache to access. + * @returns {object} Cache object identified by the cacheId or undefined if no such cache. + */ + cacheFactory.get = function(cacheId) { + return caches[cacheId]; + }; + + + return cacheFactory; + }; +} + +/** + * @ngdoc object + * @name ng.$templateCache + * + * @description + * The first time a template is used, it is loaded in the template cache for quick retrieval. You + * can load templates directly into the cache in a `script` tag, or by consuming the + * `$templateCache` service directly. + * + * Adding via the `script` tag: + *
      + * 
      + * 
      + * 
      + *         
               
       
      + *   ...
      + * 
      + * 
      + * + * **Note:** the `script` tag containing the template does not need to be included in the `head` of + * the document, but it must be below the `ng-app` definition. + * + * Adding via the $templateCache service: + * + *
      + * var myApp = angular.module('myApp', []);
      + * myApp.run(function($templateCache) {
      + *   $templateCache.put('templateId.html', 'This is the content of the template');
      + * });
      + * 
      + * + * To retrieve the template later, simply use it in your HTML: + *
      + * 
      + *
      + * + * or get it via Javascript: + *
      + * $templateCache.get('templateId.html')
      + * 
      + * + * See {@link ng.$cacheFactory $cacheFactory}. + * + */ +function $TemplateCacheProvider() { + this.$get = ['$cacheFactory', function($cacheFactory) { + return $cacheFactory('templates'); + }]; +} + +/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! + * + * DOM-related variables: + * + * - "node" - DOM Node + * - "element" - DOM Element or Node + * - "$node" or "$element" - jqLite-wrapped node or element + * + * + * Compiler related stuff: + * + * - "linkFn" - linking fn of a single directive + * - "nodeLinkFn" - function that aggregates all linking fns for a particular node + * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node + * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) + */ + + +/** + * @ngdoc function + * @name ng.$compile + * @function + * + * @description + * Compiles a piece of HTML string or DOM into a template and produces a template function, which + * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. + * + * The compilation is a process of walking the DOM tree and matching DOM elements to + * {@link ng.$compileProvider#methods_directive directives}. + * + *
      + * **Note:** This document is an in-depth reference of all directive options. + * For a gentle introduction to directives with examples of common use cases, + * see the {@link guide/directive directive guide}. + *
      + * + * ## Comprehensive Directive API + * + * There are many different options for a directive. + * + * The difference resides in the return value of the factory function. + * You can either return a "Directive Definition Object" (see below) that defines the directive properties, + * or just the `postLink` function (all other properties will have the default values). + * + *
      + * **Best Practice:** It's recommended to use the "directive definition object" form. + *
      + * + * Here's an example directive declared with a Directive Definition Object: + * + *
      + *   var myModule = angular.module(...);
      + *
      + *   myModule.directive('directiveName', function factory(injectables) {
      + *     var directiveDefinitionObject = {
      + *       priority: 0,
      + *       template: '
      ', // or // function(tElement, tAttrs) { ... }, + * // or + * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, + * replace: false, + * transclude: false, + * restrict: 'A', + * scope: false, + * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, + * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], + * compile: function compile(tElement, tAttrs, transclude) { + * return { + * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * post: function postLink(scope, iElement, iAttrs, controller) { ... } + * } + * // or + * // return function postLink( ... ) { ... } + * }, + * // or + * // link: { + * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * // post: function postLink(scope, iElement, iAttrs, controller) { ... } + * // } + * // or + * // link: function postLink( ... ) { ... } + * }; + * return directiveDefinitionObject; + * }); + *
      + * + *
      + * **Note:** Any unspecified options will use the default value. You can see the default values below. + *
      + * + * Therefore the above can be simplified as: + * + *
      + *   var myModule = angular.module(...);
      + *
      + *   myModule.directive('directiveName', function factory(injectables) {
      + *     var directiveDefinitionObject = {
      + *       link: function postLink(scope, iElement, iAttrs) { ... }
      + *     };
      + *     return directiveDefinitionObject;
      + *     // or
      + *     // return function postLink(scope, iElement, iAttrs) { ... }
      + *   });
      + * 
      + * + * + * + * ### Directive Definition Object + * + * The directive definition object provides instructions to the {@link api/ng.$compile + * compiler}. The attributes are: + * + * #### `priority` + * When there are multiple directives defined on a single DOM element, sometimes it + * is necessary to specify the order in which the directives are applied. The `priority` is used + * to sort the directives before their `compile` functions get called. Priority is defined as a + * number. Directives with greater numerical `priority` are compiled first. The order of directives with + * the same priority is undefined. The default priority is `0`. + * + * #### `terminal` + * If set to true then the current `priority` will be the last set of directives + * which will execute (any directives at the current priority will still execute + * as the order of execution on same `priority` is undefined). + * + * #### `scope` + * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the + * same element request a new scope, only one new scope is created. The new scope rule does not + * apply for the root of the template since the root of the template always gets a new scope. + * + * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from + * normal scope in that it does not prototypically inherit from the parent scope. This is useful + * when creating reusable components, which should not accidentally read or modify data in the + * parent scope. + * + * The 'isolate' scope takes an object hash which defines a set of local scope properties + * derived from the parent scope. These local properties are useful for aliasing values for + * templates. Locals definition is a hash of local scope property to its source: + * + * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is + * always a string since DOM attributes are strings. If no `attr` name is specified then the + * attribute name is assumed to be the same as the local name. + * Given `` and widget definition + * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect + * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the + * `localName` property on the widget scope. The `name` is read from the parent scope (not + * component scope). + * + * * `=` or `=attr` - set up bi-directional binding between a local scope property and the + * parent scope property of name defined via the value of the `attr` attribute. If no `attr` + * name is specified then the attribute name is assumed to be the same as the local name. + * Given `` and widget definition of + * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the + * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected + * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent + * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You + * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. + * + * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. + * If no `attr` name is specified then the attribute name is assumed to be the same as the + * local name. Given `` and widget definition of + * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to + * a function wrapper for the `count = count + value` expression. Often it's desirable to + * pass data from the isolated scope via an expression and to the parent scope, this can be + * done by passing a map of local variable names and values into the expression wrapper fn. + * For example, if the expression is `increment(amount)` then we can specify the amount value + * by calling the `localFn` as `localFn({amount: 22})`. + * + * + * + * #### `controller` + * Controller constructor function. The controller is instantiated before the + * pre-linking phase and it is shared with other directives (see + * `require` attribute). This allows the directives to communicate with each other and augment + * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: + * + * * `$scope` - Current scope associated with the element + * * `$element` - Current element + * * `$attrs` - Current attributes object for the element + * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: + * `function(cloneLinkingFn)`. + * + * + * #### `require` + * Require another directive and inject its controller as the fourth argument to the linking function. The + * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the + * injected argument will be an array in corresponding order. If no such directive can be + * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: + * + * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. + * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. + * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. + * * `?^` - Attempt to locate the required controller by searching the element's parentsor pass `null` to the + * `link` fn if not found. + * + * + * #### `controllerAs` + * Controller alias at the directive scope. An alias for the controller so it + * can be referenced at the directive template. The directive needs to define a scope for this + * configuration to be used. Useful in the case when directive is used as component. + * + * + * #### `restrict` + * String of subset of `EACM` which restricts the directive to a specific directive + * declaration style. If omitted, the default (attributes only) is used. + * + * * `E` - Element name: `` + * * `A` - Attribute (default): `
      ` + * * `C` - Class: `
      ` + * * `M` - Comment: `` + * + * + * #### `template` + * replace the current element with the contents of the HTML. The replacement process + * migrates all of the attributes / classes from the old element to the new one. See the + * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive + * Directives Guide} for an example. + * + * You can specify `template` as a string representing the template or as a function which takes + * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and + * returns a string value representing the template. + * + * + * #### `templateUrl` + * Same as `template` but the template is loaded from the specified URL. Because + * the template loading is asynchronous the compilation/linking is suspended until the template + * is loaded. + * + * You can specify `templateUrl` as a string representing the URL or as a function which takes two + * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns + * a string value representing the url. In either case, the template URL is passed through {@link + * api/ng.$sce#methods_getTrustedResourceUrl $sce.getTrustedResourceUrl}. + * + * + * #### `replace` + * specify where the template should be inserted. Defaults to `false`. + * + * * `true` - the template will replace the current element. + * * `false` - the template will replace the contents of the current element. + * + * + * #### `transclude` + * compile the content of the element and make it available to the directive. + * Typically used with {@link api/ng.directive:ngTransclude + * ngTransclude}. The advantage of transclusion is that the linking function receives a + * transclusion function which is pre-bound to the correct scope. In a typical setup the widget + * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` + * scope. This makes it possible for the widget to have private state, and the transclusion to + * be bound to the parent (pre-`isolate`) scope. + * + * * `true` - transclude the content of the directive. + * * `'element'` - transclude the whole element including any directives defined at lower priority. + * + * + * #### `compile` + * + *
      + *   function compile(tElement, tAttrs, transclude) { ... }
      + * 
      + * + * The compile function deals with transforming the template DOM. Since most directives do not do + * template transformation, it is not used often. Examples that require compile functions are + * directives that transform template DOM, such as {@link + * api/ng.directive:ngRepeat ngRepeat}, or load the contents + * asynchronously, such as {@link api/ngRoute.directive:ngView ngView}. The + * compile function takes the following arguments. + * + * * `tElement` - template element - The element where the directive has been declared. It is + * safe to do template transformation on the element and child elements only. + * + * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared + * between all directive compile functions. + * + * * `transclude` - A transclude linking function: `function(scope, cloneLinkingFn)`. + * + *
      + * **Note:** The template instance and the link instance may be different objects if the template has + * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that + * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration + * should be done in a linking function rather than in a compile function. + *
      + * + * A compile function can have a return value which can be either a function or an object. + * + * * returning a (post-link) function - is equivalent to registering the linking function via the + * `link` property of the config object when the compile function is empty. + * + * * returning an object with function(s) registered via `pre` and `post` properties - allows you to + * control when a linking function should be called during the linking phase. See info about + * pre-linking and post-linking functions below. + * + * + * #### `link` + * This property is used only if the `compile` property is not defined. + * + *
      + *   function link(scope, iElement, iAttrs, controller) { ... }
      + * 
      + * + * The link function is responsible for registering DOM listeners as well as updating the DOM. It is + * executed after the template has been cloned. This is where most of the directive logic will be + * put. + * + * * `scope` - {@link api/ng.$rootScope.Scope Scope} - The scope to be used by the + * directive for registering {@link api/ng.$rootScope.Scope#methods_$watch watches}. + * + * * `iElement` - instance element - The element where the directive is to be used. It is safe to + * manipulate the children of the element only in `postLink` function since the children have + * already been linked. + * + * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared + * between all directive linking functions. + * + * * `controller` - a controller instance - A controller instance if at least one directive on the + * element defines a controller. The controller is shared among all the directives, which allows + * the directives to use the controllers as a communication channel. + * + * + * + * #### Pre-linking function + * + * Executed before the child elements are linked. Not safe to do DOM transformation since the + * compiler linking function will fail to locate the correct elements for linking. + * + * #### Post-linking function + * + * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. + * + * + * ### Attributes + * + * The {@link api/ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the + * `link()` or `compile()` functions. It has a variety of uses. + * + * accessing *Normalized attribute names:* + * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. + * the attributes object allows for normalized access to + * the attributes. + * + * * *Directive inter-communication:* All directives share the same instance of the attributes + * object which allows the directives to use the attributes object as inter directive + * communication. + * + * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object + * allowing other directives to read the interpolated value. + * + * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes + * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also + * the only way to easily get the actual value because during the linking phase the interpolation + * hasn't been evaluated yet and so the value is at this time set to `undefined`. + * + *
      + * function linkingFn(scope, elm, attrs, ctrl) {
      + *   // get the attribute value
      + *   console.log(attrs.ngModel);
      + *
      + *   // change the attribute
      + *   attrs.$set('ngModel', 'new value');
      + *
      + *   // observe changes to interpolated attribute
      + *   attrs.$observe('ngModel', function(value) {
      + *     console.log('ngModel has changed value to ' + value);
      + *   });
      + * }
      + * 
      + * + * Below is an example using `$compileProvider`. + * + *
      + * **Note**: Typically directives are registered with `module.directive`. The example below is + * to illustrate how `$compile` works. + *
      + * + + + +
      +
      +
      +
      +
      +
      + + it('should auto compile', function() { + expect(element('div[compile]').text()).toBe('Hello Angular'); + input('html').enter('{{name}}!'); + expect(element('div[compile]').text()).toBe('Angular!'); + }); + +
      + + * + * + * @param {string|DOMElement} element Element or HTML string to compile into a template function. + * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives. + * @param {number} maxPriority only apply directives lower then given priority (Only effects the + * root element(s), not their children) + * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template + * (a DOM element/tree) to a scope. Where: + * + * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. + * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the + * `template` and call the `cloneAttachFn` function allowing the caller to attach the + * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is + * called as:
      `cloneAttachFn(clonedElement, scope)` where: + * + * * `clonedElement` - is a clone of the original `element` passed into the compiler. + * * `scope` - is the current scope with which the linking function is working with. + * + * Calling the linking function returns the element of the template. It is either the original + * element passed in, or the clone of the element if the `cloneAttachFn` is provided. + * + * After linking the view is not updated until after a call to $digest which typically is done by + * Angular automatically. + * + * If you need access to the bound view, there are two ways to do it: + * + * - If you are not asking the linking function to clone the template, create the DOM element(s) + * before you send them to the compiler and keep this reference around. + *
      + *     var element = $compile('

      {{total}}

      ')(scope); + *
      + * + * - if on the other hand, you need the element to be cloned, the view reference from the original + * example would not point to the clone, but rather to the original template that was cloned. In + * this case, you can access the clone via the cloneAttachFn: + *
      + *     var templateHTML = angular.element('

      {{total}}

      '), + * scope = ....; + * + * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) { + * //attach the clone to DOM document at the right place + * }); + * + * //now we have reference to the cloned DOM via `clone` + *
      + * + * + * For information on how the compiler works, see the + * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. + */ + +var $compileMinErr = minErr('$compile'); + +/** + * @ngdoc service + * @name ng.$compileProvider + * @function + * + * @description + */ +$CompileProvider.$inject = ['$provide']; +function $CompileProvider($provide) { + var hasDirectives = {}, + Suffix = 'Directive', + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, + aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, + imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; + + // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes + // The assumption is that future DOM event attribute names will begin with + // 'on' and be composed of only English letters. + var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; + + /** + * @ngdoc function + * @name ng.$compileProvider#directive + * @methodOf ng.$compileProvider + * @function + * + * @description + * Register a new directive with the compiler. + * + * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which + * will match as ng-bind), or an object map of directives where the keys are the + * names and the values are the factories. + * @param {function|Array} directiveFactory An injectable directive factory function. See + * {@link guide/directive} for more info. + * @returns {ng.$compileProvider} Self for chaining. + */ + this.directive = function registerDirective(name, directiveFactory) { + assertNotHasOwnProperty(name, 'directive'); + if (isString(name)) { + assertArg(directiveFactory, 'directiveFactory'); + if (!hasDirectives.hasOwnProperty(name)) { + hasDirectives[name] = []; + $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', + function($injector, $exceptionHandler) { + var directives = []; + forEach(hasDirectives[name], function(directiveFactory, index) { + try { + var directive = $injector.invoke(directiveFactory); + if (isFunction(directive)) { + directive = { compile: valueFn(directive) }; + } else if (!directive.compile && directive.link) { + directive.compile = valueFn(directive.link); + } + directive.priority = directive.priority || 0; + directive.index = index; + directive.name = directive.name || name; + directive.require = directive.require || (directive.controller && directive.name); + directive.restrict = directive.restrict || 'A'; + directives.push(directive); + } catch (e) { + $exceptionHandler(e); + } + }); + return directives; + }]); + } + hasDirectives[name].push(directiveFactory); + } else { + forEach(name, reverseParams(registerDirective)); + } + return this; + }; + + + /** + * @ngdoc function + * @name ng.$compileProvider#aHrefSanitizationWhitelist + * @methodOf ng.$compileProvider + * @function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during a[href] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to a[href] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.aHrefSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + aHrefSanitizationWhitelist = regexp; + return this; + } + return aHrefSanitizationWhitelist; + }; + + + /** + * @ngdoc function + * @name ng.$compileProvider#imgSrcSanitizationWhitelist + * @methodOf ng.$compileProvider + * @function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during img[src] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to img[src] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.imgSrcSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + imgSrcSanitizationWhitelist = regexp; + return this; + } + return imgSrcSanitizationWhitelist; + }; + + + this.$get = [ + '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', + '$controller', '$rootScope', '$document', '$sce', '$animate', + function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, + $controller, $rootScope, $document, $sce, $animate) { + + var Attributes = function(element, attr) { + this.$$element = element; + this.$attr = attr || {}; + }; + + Attributes.prototype = { + $normalize: directiveNormalize, + + + /** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$addClass + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Adds the CSS class value specified by the classVal parameter to the element. If animations + * are enabled then an animation will be triggered for the class addition. + * + * @param {string} classVal The className value that will be added to the element + */ + $addClass : function(classVal) { + if(classVal && classVal.length > 0) { + $animate.addClass(this.$$element, classVal); + } + }, + + /** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$removeClass + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Removes the CSS class value specified by the classVal parameter from the element. If + * animations are enabled then an animation will be triggered for the class removal. + * + * @param {string} classVal The className value that will be removed from the element + */ + $removeClass : function(classVal) { + if(classVal && classVal.length > 0) { + $animate.removeClass(this.$$element, classVal); + } + }, + + /** + * Set a normalized attribute on the element in a way such that all directives + * can share the attribute. This function properly handles boolean attributes. + * @param {string} key Normalized key. (ie ngAttribute) + * @param {string|boolean} value The value to set. If `null` attribute will be deleted. + * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. + * Defaults to true. + * @param {string=} attrName Optional none normalized name. Defaults to key. + */ + $set: function(key, value, writeAttr, attrName) { + //special case for class attribute addition + removal + //so that class changes can tap into the animation + //hooks provided by the $animate service + if(key == 'class') { + value = value || ''; + var current = this.$$element.attr('class') || ''; + this.$removeClass(tokenDifference(current, value).join(' ')); + this.$addClass(tokenDifference(value, current).join(' ')); + } else { + var booleanKey = getBooleanAttrName(this.$$element[0], key), + normalizedVal, + nodeName; + + if (booleanKey) { + this.$$element.prop(key, value); + attrName = booleanKey; + } + + this[key] = value; + + // translate normalized key to actual key + if (attrName) { + this.$attr[key] = attrName; + } else { + attrName = this.$attr[key]; + if (!attrName) { + this.$attr[key] = attrName = snake_case(key, '-'); + } + } + + nodeName = nodeName_(this.$$element); + + // sanitize a[href] and img[src] values + if ((nodeName === 'A' && key === 'href') || + (nodeName === 'IMG' && key === 'src')) { + // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. + if (!msie || msie >= 8 ) { + normalizedVal = urlResolve(value).href; + if (normalizedVal !== '') { + if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) || + (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) { + this[key] = value = 'unsafe:' + normalizedVal; + } + } + } + } + + if (writeAttr !== false) { + if (value === null || value === undefined) { + this.$$element.removeAttr(attrName); + } else { + this.$$element.attr(attrName, value); + } + } + } + + // fire observers + var $$observers = this.$$observers; + $$observers && forEach($$observers[key], function(fn) { + try { + fn(value); + } catch (e) { + $exceptionHandler(e); + } + }); + + function tokenDifference(str1, str2) { + var values = [], + tokens1 = str1.split(/\s+/), + tokens2 = str2.split(/\s+/); + + outer: + for(var i=0;i + forEach($compileNodes, function(node, index){ + if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { + $compileNodes[index] = node = jqLite(node).wrap('').parent()[0]; + } + }); + var compositeLinkFn = + compileNodes($compileNodes, transcludeFn, $compileNodes, + maxPriority, ignoreDirective, previousCompileContext); + return function publicLinkFn(scope, cloneConnectFn){ + assertArg(scope, 'scope'); + // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart + // and sometimes changes the structure of the DOM. + var $linkNode = cloneConnectFn + ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! + : $compileNodes; + + // Attach scope only to non-text nodes. + for(var i = 0, ii = $linkNode.length; i + addDirective(directives, + directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); + + // iterate over the attributes + for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, + j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { + var attrStartName = false; + var attrEndName = false; + + attr = nAttrs[j]; + if (!msie || msie >= 8 || attr.specified) { + name = attr.name; + // support ngAttr attribute binding + ngAttrName = directiveNormalize(name); + if (NG_ATTR_BINDING.test(ngAttrName)) { + name = snake_case(ngAttrName.substr(6), '-'); + } + + var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); + if (ngAttrName === directiveNName + 'Start') { + attrStartName = name; + attrEndName = name.substr(0, name.length - 5) + 'end'; + name = name.substr(0, name.length - 6); + } + + nName = directiveNormalize(name.toLowerCase()); + attrsMap[nName] = name; + attrs[nName] = value = trim((msie && name == 'href') + ? decodeURIComponent(node.getAttribute(name, 2)) + : attr.value); + if (getBooleanAttrName(node, nName)) { + attrs[nName] = true; // presence means true + } + addAttrInterpolateDirective(node, directives, value, nName); + addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, + attrEndName); + } + } + + // use class as directive + className = node.className; + if (isString(className) && className !== '') { + while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { + nName = directiveNormalize(match[2]); + if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { + attrs[nName] = trim(match[3]); + } + className = className.substr(match.index + match[0].length); + } + } + break; + case 3: /* Text Node */ + addTextInterpolateDirective(directives, node.nodeValue); + break; + case 8: /* Comment */ + try { + match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); + if (match) { + nName = directiveNormalize(match[1]); + if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { + attrs[nName] = trim(match[2]); + } + } + } catch (e) { + // turns out that under some circumstances IE9 throws errors when one attempts to read + // comment's node value. + // Just ignore it and continue. (Can't seem to reproduce in test case.) + } + break; + } + + directives.sort(byPriority); + return directives; + } + + /** + * Given a node with an directive-start it collects all of the siblings until it finds + * directive-end. + * @param node + * @param attrStart + * @param attrEnd + * @returns {*} + */ + function groupScan(node, attrStart, attrEnd) { + var nodes = []; + var depth = 0; + if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { + var startNode = node; + do { + if (!node) { + throw $compileMinErr('uterdir', + "Unterminated attribute, found '{0}' but no matching '{1}' found.", + attrStart, attrEnd); + } + if (node.nodeType == 1 /** Element **/) { + if (node.hasAttribute(attrStart)) depth++; + if (node.hasAttribute(attrEnd)) depth--; + } + nodes.push(node); + node = node.nextSibling; + } while (depth > 0); + } else { + nodes.push(node); + } + + return jqLite(nodes); + } + + /** + * Wrapper for linking function which converts normal linking function into a grouped + * linking function. + * @param linkFn + * @param attrStart + * @param attrEnd + * @returns {Function} + */ + function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { + return function(scope, element, attrs, controllers) { + element = groupScan(element[0], attrStart, attrEnd); + return linkFn(scope, element, attrs, controllers); + }; + } + + /** + * Once the directives have been collected, their compile functions are executed. This method + * is responsible for inlining directive templates as well as terminating the application + * of the directives if the terminal directive has been reached. + * + * @param {Array} directives Array of collected directives to execute their compile function. + * this needs to be pre-sorted by priority order. + * @param {Node} compileNode The raw DOM node to apply the compile functions to + * @param {Object} templateAttrs The shared attribute function + * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the + * scope argument is auto-generated to the new + * child of the transcluded parent scope. + * @param {JQLite} jqCollection If we are working on the root of the compile tree then this + * argument has the root jqLite array so that we can replace nodes + * on it. + * @param {Object=} originalReplaceDirective An optional directive that will be ignored when + * compiling the transclusion. + * @param {Array.} preLinkFns + * @param {Array.} postLinkFns + * @param {Object} previousCompileContext Context used for previous compilation of the current + * node + * @returns linkFn + */ + function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, + jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, + previousCompileContext) { + previousCompileContext = previousCompileContext || {}; + + var terminalPriority = -Number.MAX_VALUE, + newScopeDirective, + controllerDirectives = previousCompileContext.controllerDirectives, + newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, + templateDirective = previousCompileContext.templateDirective, + transcludeDirective = previousCompileContext.transcludeDirective, + $compileNode = templateAttrs.$$element = jqLite(compileNode), + directive, + directiveName, + $template, + replaceDirective = originalReplaceDirective, + childTranscludeFn = transcludeFn, + linkFn, + directiveValue; + + // executes all directives on the current element + for(var i = 0, ii = directives.length; i < ii; i++) { + directive = directives[i]; + var attrStart = directive.$$start; + var attrEnd = directive.$$end; + + // collect multiblock sections + if (attrStart) { + $compileNode = groupScan(compileNode, attrStart, attrEnd); + } + $template = undefined; + + if (terminalPriority > directive.priority) { + break; // prevent further processing of directives + } + + if (directiveValue = directive.scope) { + newScopeDirective = newScopeDirective || directive; + + // skip the check for directives with async templates, we'll check the derived sync + // directive when the template arrives + if (!directive.templateUrl) { + assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, + $compileNode); + if (isObject(directiveValue)) { + newIsolateScopeDirective = directive; + } + } + } + + directiveName = directive.name; + + if (!directive.templateUrl && directive.controller) { + directiveValue = directive.controller; + controllerDirectives = controllerDirectives || {}; + assertNoDuplicate("'" + directiveName + "' controller", + controllerDirectives[directiveName], directive, $compileNode); + controllerDirectives[directiveName] = directive; + } + + if (directiveValue = directive.transclude) { + // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. + // This option should only be used by directives that know how to how to safely handle element transclusion, + // where the transcluded nodes are added or replaced after linking. + if (!directive.$$tlb) { + assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); + transcludeDirective = directive; + } + + if (directiveValue == 'element') { + terminalPriority = directive.priority; + $template = groupScan(compileNode, attrStart, attrEnd); + $compileNode = templateAttrs.$$element = + jqLite(document.createComment(' ' + directiveName + ': ' + + templateAttrs[directiveName] + ' ')); + compileNode = $compileNode[0]; + replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); + + childTranscludeFn = compile($template, transcludeFn, terminalPriority, + replaceDirective && replaceDirective.name, { + // Don't pass in: + // - controllerDirectives - otherwise we'll create duplicates controllers + // - newIsolateScopeDirective or templateDirective - combining templates with + // element transclusion doesn't make sense. + // + // We need only transcludeDirective so that we prevent putting transclusion + // on the same element more than once. + transcludeDirective: transcludeDirective + }); + } else { + $template = jqLite(jqLiteClone(compileNode)).contents(); + $compileNode.html(''); // clear contents + childTranscludeFn = compile($template, transcludeFn); + } + } + + if (directive.template) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + + directiveValue = (isFunction(directive.template)) + ? directive.template($compileNode, templateAttrs) + : directive.template; + + directiveValue = denormalizeTemplate(directiveValue); + + if (directive.replace) { + replaceDirective = directive; + $template = jqLite('
      ' + + trim(directiveValue) + + '
      ').contents(); + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw $compileMinErr('tplrt', + "Template for directive '{0}' must have exactly one root element. {1}", + directiveName, ''); + } + + replaceWith(jqCollection, $compileNode, compileNode); + + var newTemplateAttrs = {$attr: {}}; + + // combine directives from the original node and from the template: + // - take the array of directives for this element + // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) + // - collect directives from the template and sort them by priority + // - combine directives as: processed + template + unprocessed + var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); + var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); + + if (newIsolateScopeDirective) { + markDirectivesAsIsolate(templateDirectives); + } + directives = directives.concat(templateDirectives).concat(unprocessedDirectives); + mergeTemplateAttributes(templateAttrs, newTemplateAttrs); + + ii = directives.length; + } else { + $compileNode.html(directiveValue); + } + } + + if (directive.templateUrl) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + + if (directive.replace) { + replaceDirective = directive; + } + + nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, + templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { + controllerDirectives: controllerDirectives, + newIsolateScopeDirective: newIsolateScopeDirective, + templateDirective: templateDirective, + transcludeDirective: transcludeDirective + }); + ii = directives.length; + } else if (directive.compile) { + try { + linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); + if (isFunction(linkFn)) { + addLinkFns(null, linkFn, attrStart, attrEnd); + } else if (linkFn) { + addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); + } + } catch (e) { + $exceptionHandler(e, startingTag($compileNode)); + } + } + + if (directive.terminal) { + nodeLinkFn.terminal = true; + terminalPriority = Math.max(terminalPriority, directive.priority); + } + + } + + nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; + nodeLinkFn.transclude = transcludeDirective && childTranscludeFn; + + // might be normal or delayed nodeLinkFn depending on if templateUrl is present + return nodeLinkFn; + + //////////////////// + + function addLinkFns(pre, post, attrStart, attrEnd) { + if (pre) { + if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); + pre.require = directive.require; + if (newIsolateScopeDirective === directive || directive.$$isolateScope) { + pre = cloneAndAnnotateFn(pre, {isolateScope: true}); + } + preLinkFns.push(pre); + } + if (post) { + if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); + post.require = directive.require; + if (newIsolateScopeDirective === directive || directive.$$isolateScope) { + post = cloneAndAnnotateFn(post, {isolateScope: true}); + } + postLinkFns.push(post); + } + } + + + function getControllers(require, $element) { + var value, retrievalMethod = 'data', optional = false; + if (isString(require)) { + while((value = require.charAt(0)) == '^' || value == '?') { + require = require.substr(1); + if (value == '^') { + retrievalMethod = 'inheritedData'; + } + optional = optional || value == '?'; + } + + value = $element[retrievalMethod]('$' + require + 'Controller'); + + if ($element[0].nodeType == 8 && $element[0].$$controller) { // Transclusion comment node + value = value || $element[0].$$controller; + $element[0].$$controller = null; + } + + if (!value && !optional) { + throw $compileMinErr('ctreq', + "Controller '{0}', required by directive '{1}', can't be found!", + require, directiveName); + } + return value; + } else if (isArray(require)) { + value = []; + forEach(require, function(require) { + value.push(getControllers(require, $element)); + }); + } + return value; + } + + + function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { + var attrs, $element, i, ii, linkFn, controller, isolateScope; + + if (compileNode === linkNode) { + attrs = templateAttrs; + } else { + attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); + } + $element = attrs.$$element; + + if (newIsolateScopeDirective) { + var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; + var $linkNode = jqLite(linkNode); + + isolateScope = scope.$new(true); + + if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { + $linkNode.data('$isolateScope', isolateScope) ; + } else { + $linkNode.data('$isolateScopeNoTemplate', isolateScope); + } + + + + safeAddClass($linkNode, 'ng-isolate-scope'); + + forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { + var match = definition.match(LOCAL_REGEXP) || [], + attrName = match[3] || scopeName, + optional = (match[2] == '?'), + mode = match[1], // @, =, or & + lastValue, + parentGet, parentSet; + + isolateScope.$$isolateBindings[scopeName] = mode + attrName; + + switch (mode) { + + case '@': + attrs.$observe(attrName, function(value) { + isolateScope[scopeName] = value; + }); + attrs.$$observers[attrName].$$scope = scope; + if( attrs[attrName] ) { + // If the attribute has been provided then we trigger an interpolation to ensure + // the value is there for use in the link fn + isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); + } + break; + + case '=': + if (optional && !attrs[attrName]) { + return; + } + parentGet = $parse(attrs[attrName]); + parentSet = parentGet.assign || function() { + // reset the change, or we will throw this exception on every $digest + lastValue = isolateScope[scopeName] = parentGet(scope); + throw $compileMinErr('nonassign', + "Expression '{0}' used with directive '{1}' is non-assignable!", + attrs[attrName], newIsolateScopeDirective.name); + }; + lastValue = isolateScope[scopeName] = parentGet(scope); + isolateScope.$watch(function parentValueWatch() { + var parentValue = parentGet(scope); + + if (parentValue !== isolateScope[scopeName]) { + // we are out of sync and need to copy + if (parentValue !== lastValue) { + // parent changed and it has precedence + lastValue = isolateScope[scopeName] = parentValue; + } else { + // if the parent can be assigned then do so + parentSet(scope, parentValue = lastValue = isolateScope[scopeName]); + } + } + return parentValue; + }); + break; + + case '&': + parentGet = $parse(attrs[attrName]); + isolateScope[scopeName] = function(locals) { + return parentGet(scope, locals); + }; + break; + + default: + throw $compileMinErr('iscp', + "Invalid isolate scope definition for directive '{0}'." + + " Definition: {... {1}: '{2}' ...}", + newIsolateScopeDirective.name, scopeName, definition); + } + }); + } + + if (controllerDirectives) { + forEach(controllerDirectives, function(directive) { + var locals = { + $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, + $element: $element, + $attrs: attrs, + $transclude: boundTranscludeFn + }, controllerInstance; + + controller = directive.controller; + if (controller == '@') { + controller = attrs[directive.name]; + } + + controllerInstance = $controller(controller, locals); + + // Directives with element transclusion and a controller need to attach controller + // to the comment node created by the compiler, but jQuery .data doesn't support + // attaching data to comment nodes so instead we set it directly on the element and + // remove it after we read it later. + if ($element[0].nodeType == 8) { // Transclusion comment node + $element[0].$$controller = controllerInstance; + } else { + $element.data('$' + directive.name + 'Controller', controllerInstance); + } + if (directive.controllerAs) { + locals.$scope[directive.controllerAs] = controllerInstance; + } + }); + } + + // PRELINKING + for(i = 0, ii = preLinkFns.length; i < ii; i++) { + try { + linkFn = preLinkFns[i]; + linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, + linkFn.require && getControllers(linkFn.require, $element)); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + + // RECURSION + // We only pass the isolate scope, if the isolate directive has a template, + // otherwise the child elements do not belong to the isolate directive. + var scopeToChild = scope; + if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { + scopeToChild = isolateScope; + } + childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); + + // POSTLINKING + for(i = postLinkFns.length - 1; i >= 0; i--) { + try { + linkFn = postLinkFns[i]; + linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, + linkFn.require && getControllers(linkFn.require, $element)); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + } + } + + function markDirectivesAsIsolate(directives) { + // mark all directives as needing isolate scope. + for (var j = 0, jj = directives.length; j < jj; j++) { + directives[j] = inherit(directives[j], {$$isolateScope: true}); + } + } + + /** + * looks up the directive and decorates it with exception handling and proper parameters. We + * call this the boundDirective. + * + * @param {string} name name of the directive to look up. + * @param {string} location The directive must be found in specific format. + * String containing any of theses characters: + * + * * `E`: element name + * * `A': attribute + * * `C`: class + * * `M`: comment + * @returns true if directive was added. + */ + function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, + endAttrName) { + if (name === ignoreDirective) return null; + var match = null; + if (hasDirectives.hasOwnProperty(name)) { + for(var directive, directives = $injector.get(name + Suffix), + i = 0, ii = directives.length; i directive.priority) && + directive.restrict.indexOf(location) != -1) { + if (startAttrName) { + directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); + } + tDirectives.push(directive); + match = directive; + } + } catch(e) { $exceptionHandler(e); } + } + } + return match; + } + + + /** + * When the element is replaced with HTML template then the new attributes + * on the template need to be merged with the existing attributes in the DOM. + * The desired effect is to have both of the attributes present. + * + * @param {object} dst destination attributes (original DOM) + * @param {object} src source attributes (from the directive template) + */ + function mergeTemplateAttributes(dst, src) { + var srcAttr = src.$attr, + dstAttr = dst.$attr, + $element = dst.$$element; + + // reapply the old attributes to the new element + forEach(dst, function(value, key) { + if (key.charAt(0) != '$') { + if (src[key]) { + value += (key === 'style' ? ';' : ' ') + src[key]; + } + dst.$set(key, value, true, srcAttr[key]); + } + }); + + // copy the new attributes on the old attrs object + forEach(src, function(value, key) { + if (key == 'class') { + safeAddClass($element, value); + dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; + } else if (key == 'style') { + $element.attr('style', $element.attr('style') + ';' + value); + // `dst` will never contain hasOwnProperty as DOM parser won't let it. + // You will get an "InvalidCharacterError: DOM Exception 5" error if you + // have an attribute like "has-own-property" or "data-has-own-property", etc. + } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { + dst[key] = value; + dstAttr[key] = srcAttr[key]; + } + }); + } + + + function compileTemplateUrl(directives, $compileNode, tAttrs, + $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { + var linkQueue = [], + afterTemplateNodeLinkFn, + afterTemplateChildLinkFn, + beforeTemplateCompileNode = $compileNode[0], + origAsyncDirective = directives.shift(), + // The fact that we have to copy and patch the directive seems wrong! + derivedSyncDirective = extend({}, origAsyncDirective, { + templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective + }), + templateUrl = (isFunction(origAsyncDirective.templateUrl)) + ? origAsyncDirective.templateUrl($compileNode, tAttrs) + : origAsyncDirective.templateUrl; + + $compileNode.html(''); + + $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). + success(function(content) { + var compileNode, tempTemplateAttrs, $template; + + content = denormalizeTemplate(content); + + if (origAsyncDirective.replace) { + $template = jqLite('
      ' + trim(content) + '
      ').contents(); + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw $compileMinErr('tplrt', + "Template for directive '{0}' must have exactly one root element. {1}", + origAsyncDirective.name, templateUrl); + } + + tempTemplateAttrs = {$attr: {}}; + replaceWith($rootElement, $compileNode, compileNode); + var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); + + if (isObject(origAsyncDirective.scope)) { + markDirectivesAsIsolate(templateDirectives); + } + directives = templateDirectives.concat(directives); + mergeTemplateAttributes(tAttrs, tempTemplateAttrs); + } else { + compileNode = beforeTemplateCompileNode; + $compileNode.html(content); + } + + directives.unshift(derivedSyncDirective); + + afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, + childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, + previousCompileContext); + forEach($rootElement, function(node, i) { + if (node == compileNode) { + $rootElement[i] = $compileNode[0]; + } + }); + afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); + + + while(linkQueue.length) { + var scope = linkQueue.shift(), + beforeTemplateLinkNode = linkQueue.shift(), + linkRootElement = linkQueue.shift(), + controller = linkQueue.shift(), + linkNode = $compileNode[0]; + + if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { + // it was cloned therefore we have to clone as well. + linkNode = jqLiteClone(compileNode); + replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); + } + + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, + controller); + } + linkQueue = null; + }). + error(function(response, code, headers, config) { + throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); + }); + + return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) { + if (linkQueue) { + linkQueue.push(scope); + linkQueue.push(node); + linkQueue.push(rootElement); + linkQueue.push(controller); + } else { + afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller); + } + }; + } + + + /** + * Sorting function for bound directives. + */ + function byPriority(a, b) { + var diff = b.priority - a.priority; + if (diff !== 0) return diff; + if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; + return a.index - b.index; + } + + + function assertNoDuplicate(what, previousDirective, directive, element) { + if (previousDirective) { + throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', + previousDirective.name, directive.name, what, startingTag(element)); + } + } + + + function addTextInterpolateDirective(directives, text) { + var interpolateFn = $interpolate(text, true); + if (interpolateFn) { + directives.push({ + priority: 0, + compile: valueFn(function textInterpolateLinkFn(scope, node) { + var parent = node.parent(), + bindings = parent.data('$binding') || []; + bindings.push(interpolateFn); + safeAddClass(parent.data('$binding', bindings), 'ng-binding'); + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { + node[0].nodeValue = value; + }); + }) + }); + } + } + + + function getTrustedContext(node, attrNormalizedName) { + // maction[xlink:href] can source SVG. It's not limited to . + if (attrNormalizedName == "xlinkHref" || + (nodeName_(node) != "IMG" && (attrNormalizedName == "src" || + attrNormalizedName == "ngSrc"))) { + return $sce.RESOURCE_URL; + } + } + + + function addAttrInterpolateDirective(node, directives, value, name) { + var interpolateFn = $interpolate(value, true); + + // no interpolation found -> ignore + if (!interpolateFn) return; + + + if (name === "multiple" && nodeName_(node) === "SELECT") { + throw $compileMinErr("selmulti", + "Binding to the 'multiple' attribute is not supported. Element: {0}", + startingTag(node)); + } + + directives.push({ + priority: 100, + compile: function() { + return { + pre: function attrInterpolatePreLinkFn(scope, element, attr) { + var $$observers = (attr.$$observers || (attr.$$observers = {})); + + if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { + throw $compileMinErr('nodomevents', + "Interpolations for HTML DOM event attributes are disallowed. Please use the " + + "ng- versions (such as ng-click instead of onclick) instead."); + } + + // we need to interpolate again, in case the attribute value has been updated + // (e.g. by another directive's compile function) + interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); + + // if attribute was updated so that there is no interpolation going on we don't want to + // register any observers + if (!interpolateFn) return; + + // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the + // actual attr value + attr[name] = interpolateFn(scope); + ($$observers[name] || ($$observers[name] = [])).$$inter = true; + (attr.$$observers && attr.$$observers[name].$$scope || scope). + $watch(interpolateFn, function interpolateFnWatchAction(value) { + attr.$set(name, value); + }); + } + }; + } + }); + } + + + /** + * This is a special jqLite.replaceWith, which can replace items which + * have no parents, provided that the containing jqLite collection is provided. + * + * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes + * in the root of the tree. + * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep + * the shell, but replace its DOM node reference. + * @param {Node} newNode The new DOM node. + */ + function replaceWith($rootElement, elementsToRemove, newNode) { + var firstElementToRemove = elementsToRemove[0], + removeCount = elementsToRemove.length, + parent = firstElementToRemove.parentNode, + i, ii; + + if ($rootElement) { + for(i = 0, ii = $rootElement.length; i < ii; i++) { + if ($rootElement[i] == firstElementToRemove) { + $rootElement[i++] = newNode; + for (var j = i, j2 = j + removeCount - 1, + jj = $rootElement.length; + j < jj; j++, j2++) { + if (j2 < jj) { + $rootElement[j] = $rootElement[j2]; + } else { + delete $rootElement[j]; + } + } + $rootElement.length -= removeCount - 1; + break; + } + } + } + + if (parent) { + parent.replaceChild(newNode, firstElementToRemove); + } + var fragment = document.createDocumentFragment(); + fragment.appendChild(firstElementToRemove); + newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; + for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { + var element = elementsToRemove[k]; + jqLite(element).remove(); // must do this way to clean up expando + fragment.appendChild(element); + delete elementsToRemove[k]; + } + + elementsToRemove[0] = newNode; + elementsToRemove.length = 1; + } + + + function cloneAndAnnotateFn(fn, annotation) { + return extend(function() { return fn.apply(null, arguments); }, fn, annotation); + } + }]; +} + +var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; +/** + * Converts all accepted directives format into proper directive name. + * All of these will become 'myDirective': + * my:Directive + * my-directive + * x-my-directive + * data-my:directive + * + * Also there is special case for Moz prefix starting with upper case letter. + * @param name Name to normalize + */ +function directiveNormalize(name) { + return camelCase(name.replace(PREFIX_REGEXP, '')); +} + +/** + * @ngdoc object + * @name ng.$compile.directive.Attributes + * + * @description + * A shared object between directive compile / linking functions which contains normalized DOM + * element attributes. The values reflect current binding state `{{ }}`. The normalization is + * needed since all of these are treated as equivalent in Angular: + * + * + */ + +/** + * @ngdoc property + * @name ng.$compile.directive.Attributes#$attr + * @propertyOf ng.$compile.directive.Attributes + * @returns {object} A map of DOM element attribute names to the normalized name. This is + * needed to do reverse lookup from normalized name back to actual name. + */ + + +/** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$set + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Set DOM element attribute value. + * + * + * @param {string} name Normalized element attribute name of the property to modify. The name is + * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr} + * property to the original name. + * @param {string} value Value to set the attribute to. The value can be an interpolated string. + */ + + + +/** + * Closure compiler type information + */ + +function nodesetLinkingFn( + /* angular.Scope */ scope, + /* NodeList */ nodeList, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +){} + +function directiveLinkingFn( + /* nodesetLinkingFn */ nodesetLinkingFn, + /* angular.Scope */ scope, + /* Node */ node, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +){} + +/** + * @ngdoc object + * @name ng.$controllerProvider + * @description + * The {@link ng.$controller $controller service} is used by Angular to create new + * controllers. + * + * This provider allows controller registration via the + * {@link ng.$controllerProvider#methods_register register} method. + */ +function $ControllerProvider() { + var controllers = {}, + CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; + + + /** + * @ngdoc function + * @name ng.$controllerProvider#register + * @methodOf ng.$controllerProvider + * @param {string|Object} name Controller name, or an object map of controllers where the keys are + * the names and the values are the constructors. + * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI + * annotations in the array notation). + */ + this.register = function(name, constructor) { + assertNotHasOwnProperty(name, 'controller'); + if (isObject(name)) { + extend(controllers, name); + } else { + controllers[name] = constructor; + } + }; + + + this.$get = ['$injector', '$window', function($injector, $window) { + + /** + * @ngdoc function + * @name ng.$controller + * @requires $injector + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * check `window[constructor]` on the global `window` object + * + * @param {Object} locals Injection locals for Controller. + * @return {Object} Instance of given controller. + * + * @description + * `$controller` service is responsible for instantiating controllers. + * + * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into + * a service, so that one can override this service with {@link https://gist.github.com/1649788 + * BC version}. + */ + return function(expression, locals) { + var instance, match, constructor, identifier; + + if(isString(expression)) { + match = expression.match(CNTRL_REG), + constructor = match[1], + identifier = match[3]; + expression = controllers.hasOwnProperty(constructor) + ? controllers[constructor] + : getter(locals.$scope, constructor, true) || getter($window, constructor, true); + + assertArgFn(expression, constructor, true); + } + + instance = $injector.instantiate(expression, locals); + + if (identifier) { + if (!(locals && typeof locals.$scope == 'object')) { + throw minErr('$controller')('noscp', + "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", + constructor || expression.name, identifier); + } + + locals.$scope[identifier] = instance; + } + + return instance; + }; + }]; +} + +/** + * @ngdoc object + * @name ng.$document + * @requires $window + * + * @description + * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document` + * element. + */ +function $DocumentProvider(){ + this.$get = ['$window', function(window){ + return jqLite(window.document); + }]; +} + +/** + * @ngdoc function + * @name ng.$exceptionHandler + * @requires $log + * + * @description + * Any uncaught exception in angular expressions is delegated to this service. + * The default implementation simply delegates to `$log.error` which logs it into + * the browser console. + * + * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by + * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. + * + * ## Example: + * + *
      + *   angular.module('exceptionOverride', []).factory('$exceptionHandler', function () {
      + *     return function (exception, cause) {
      + *       exception.message += ' (caused by "' + cause + '")';
      + *       throw exception;
      + *     };
      + *   });
      + * 
      + * + * This example will override the normal action of `$exceptionHandler`, to make angular + * exceptions fail hard when they happen, instead of just logging to the console. + * + * @param {Error} exception Exception associated with the error. + * @param {string=} cause optional information about the context in which + * the error was thrown. + * + */ +function $ExceptionHandlerProvider() { + this.$get = ['$log', function($log) { + return function(exception, cause) { + $log.error.apply($log, arguments); + }; + }]; +} + +/** + * Parse headers into key value object + * + * @param {string} headers Raw headers as a string + * @returns {Object} Parsed headers as key value object + */ +function parseHeaders(headers) { + var parsed = {}, key, val, i; + + if (!headers) return parsed; + + forEach(headers.split('\n'), function(line) { + i = line.indexOf(':'); + key = lowercase(trim(line.substr(0, i))); + val = trim(line.substr(i + 1)); + + if (key) { + if (parsed[key]) { + parsed[key] += ', ' + val; + } else { + parsed[key] = val; + } + } + }); + + return parsed; +} + + +/** + * Returns a function that provides access to parsed headers. + * + * Headers are lazy parsed when first requested. + * @see parseHeaders + * + * @param {(string|Object)} headers Headers to provide access to. + * @returns {function(string=)} Returns a getter function which if called with: + * + * - if called with single an argument returns a single header value or null + * - if called with no arguments returns an object containing all headers. + */ +function headersGetter(headers) { + var headersObj = isObject(headers) ? headers : undefined; + + return function(name) { + if (!headersObj) headersObj = parseHeaders(headers); + + if (name) { + return headersObj[lowercase(name)] || null; + } + + return headersObj; + }; +} + + +/** + * Chain all given functions + * + * This function is used for both request and response transforming + * + * @param {*} data Data to transform. + * @param {function(string=)} headers Http headers getter fn. + * @param {(function|Array.)} fns Function or an array of functions. + * @returns {*} Transformed data. + */ +function transformData(data, headers, fns) { + if (isFunction(fns)) + return fns(data, headers); + + forEach(fns, function(fn) { + data = fn(data, headers); + }); + + return data; +} + + +function isSuccess(status) { + return 200 <= status && status < 300; +} + + +function $HttpProvider() { + var JSON_START = /^\s*(\[|\{[^\{])/, + JSON_END = /[\}\]]\s*$/, + PROTECTION_PREFIX = /^\)\]\}',?\n/, + CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; + + var defaults = this.defaults = { + // transform incoming response data + transformResponse: [function(data) { + if (isString(data)) { + // strip json vulnerability protection prefix + data = data.replace(PROTECTION_PREFIX, ''); + if (JSON_START.test(data) && JSON_END.test(data)) + data = fromJson(data); + } + return data; + }], + + // transform outgoing request data + transformRequest: [function(d) { + return isObject(d) && !isFile(d) ? toJson(d) : d; + }], + + // default headers + headers: { + common: { + 'Accept': 'application/json, text/plain, */*' + }, + post: CONTENT_TYPE_APPLICATION_JSON, + put: CONTENT_TYPE_APPLICATION_JSON, + patch: CONTENT_TYPE_APPLICATION_JSON + }, + + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN' + }; + + /** + * Are ordered by request, i.e. they are applied in the same order as the + * array, on request, but reverse order, on response. + */ + var interceptorFactories = this.interceptors = []; + + /** + * For historical reasons, response interceptors are ordered by the order in which + * they are applied to the response. (This is the opposite of interceptorFactories) + */ + var responseInterceptorFactories = this.responseInterceptors = []; + + this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', + function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { + + var defaultCache = $cacheFactory('$http'); + + /** + * Interceptors stored in reverse order. Inner interceptors before outer interceptors. + * The reversal is needed so that we can build up the interception chain around the + * server request. + */ + var reversedInterceptors = []; + + forEach(interceptorFactories, function(interceptorFactory) { + reversedInterceptors.unshift(isString(interceptorFactory) + ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); + }); + + forEach(responseInterceptorFactories, function(interceptorFactory, index) { + var responseFn = isString(interceptorFactory) + ? $injector.get(interceptorFactory) + : $injector.invoke(interceptorFactory); + + /** + * Response interceptors go before "around" interceptors (no real reason, just + * had to pick one.) But they are already reversed, so we can't use unshift, hence + * the splice. + */ + reversedInterceptors.splice(index, 0, { + response: function(response) { + return responseFn($q.when(response)); + }, + responseError: function(response) { + return responseFn($q.reject(response)); + } + }); + }); + + + /** + * @ngdoc function + * @name ng.$http + * @requires $httpBackend + * @requires $browser + * @requires $cacheFactory + * @requires $rootScope + * @requires $q + * @requires $injector + * + * @description + * The `$http` service is a core Angular service that facilitates communication with the remote + * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest + * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. + * + * For unit testing applications that use `$http` service, see + * {@link ngMock.$httpBackend $httpBackend mock}. + * + * For a higher level of abstraction, please check out the {@link ngResource.$resource + * $resource} service. + * + * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by + * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage + * it is important to familiarize yourself with these APIs and the guarantees they provide. + * + * + * # General usage + * The `$http` service is a function which takes a single argument — a configuration object — + * that is used to generate an HTTP request and returns a {@link ng.$q promise} + * with two $http specific methods: `success` and `error`. + * + *
      +     *   $http({method: 'GET', url: '/someUrl'}).
      +     *     success(function(data, status, headers, config) {
      +     *       // this callback will be called asynchronously
      +     *       // when the response is available
      +     *     }).
      +     *     error(function(data, status, headers, config) {
      +     *       // called asynchronously if an error occurs
      +     *       // or server returns response with an error status.
      +     *     });
      +     * 
      + * + * Since the returned value of calling the $http function is a `promise`, you can also use + * the `then` method to register callbacks, and these callbacks will receive a single argument – + * an object representing the response. See the API signature and type info below for more + * details. + * + * A response status code between 200 and 299 is considered a success status and + * will result in the success callback being called. Note that if the response is a redirect, + * XMLHttpRequest will transparently follow it, meaning that the error callback will not be + * called for such responses. + * + * # Calling $http from outside AngularJS + * The `$http` service will not actually send the request until the next `$digest()` is + * executed. Normally this is not an issue, since almost all the time your call to `$http` will + * be from within a `$apply()` block. + * If you are calling `$http` from outside Angular, then you should wrap it in a call to + * `$apply` to cause a $digest to occur and also to handle errors in the block correctly. + * + * ``` + * $scope.$apply(function() { + * $http(...); + * }); + * ``` + * + * # Writing Unit Tests that use $http + * When unit testing you are mostly responsible for scheduling the `$digest` cycle. If you do + * not trigger a `$digest` before calling `$httpBackend.flush()` then the request will not have + * been made and `$httpBackend.expect(...)` expectations will fail. The solution is to run the + * code that calls the `$http()` method inside a $apply block as explained in the previous + * section. + * + * ``` + * $httpBackend.expectGET(...); + * $scope.$apply(function() { + * $http.get(...); + * }); + * $httpBackend.flush(); + * ``` + * + * # Shortcut methods + * + * Since all invocations of the $http service require passing in an HTTP method and URL, and + * POST/PUT requests require request data to be provided as well, shortcut methods + * were created: + * + *
      +     *   $http.get('/someUrl').success(successCallback);
      +     *   $http.post('/someUrl', data).success(successCallback);
      +     * 
      + * + * Complete list of shortcut methods: + * + * - {@link ng.$http#methods_get $http.get} + * - {@link ng.$http#methods_head $http.head} + * - {@link ng.$http#methods_post $http.post} + * - {@link ng.$http#methods_put $http.put} + * - {@link ng.$http#methods_delete $http.delete} + * - {@link ng.$http#methods_jsonp $http.jsonp} + * + * + * # Setting HTTP Headers + * + * The $http service will automatically add certain HTTP headers to all requests. These defaults + * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration + * object, which currently contains this default configuration: + * + * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): + * - `Accept: application/json, text/plain, * / *` + * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) + * - `Content-Type: application/json` + * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) + * - `Content-Type: application/json` + * + * To add or overwrite these defaults, simply add or remove a property from these configuration + * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object + * with the lowercased HTTP method name as the key, e.g. + * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. + * + * The defaults can also be set at runtime via the `$http.defaults` object in the same + * fashion. In addition, you can supply a `headers` property in the config object passed when + * calling `$http(config)`, which overrides the defaults without changing them globally. + * + * + * # Transforming Requests and Responses + * + * Both requests and responses can be transformed using transform functions. By default, Angular + * applies these transformations: + * + * Request transformations: + * + * - If the `data` property of the request configuration object contains an object, serialize it + * into JSON format. + * + * Response transformations: + * + * - If XSRF prefix is detected, strip it (see Security Considerations section below). + * - If JSON response is detected, deserialize it using a JSON parser. + * + * To globally augment or override the default transforms, modify the + * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` + * properties. These properties are by default an array of transform functions, which allows you + * to `push` or `unshift` a new transformation function into the transformation chain. You can + * also decide to completely override any default transformations by assigning your + * transformation functions to these properties directly without the array wrapper. + * + * Similarly, to locally override the request/response transforms, augment the + * `transformRequest` and/or `transformResponse` properties of the configuration object passed + * into `$http`. + * + * + * # Caching + * + * To enable caching, set the configuration property `cache` to `true`. When the cache is + * enabled, `$http` stores the response from the server in local cache. Next time the + * response is served from the cache without sending a request to the server. + * + * Note that even if the response is served from cache, delivery of the data is asynchronous in + * the same way that real requests are. + * + * If there are multiple GET requests for the same URL that should be cached using the same + * cache, but the cache is not populated yet, only one request to the server will be made and + * the remaining requests will be fulfilled using the response from the first request. + * + * A custom default cache built with $cacheFactory can be provided in $http.defaults.cache. + * To skip it, set configuration property `cache` to `false`. + * + * + * # Interceptors + * + * Before you start creating interceptors, be sure to understand the + * {@link ng.$q $q and deferred/promise APIs}. + * + * For purposes of global error handling, authentication, or any kind of synchronous or + * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be + * able to intercept requests before they are handed to the server and + * responses before they are handed over to the application code that + * initiated these requests. The interceptors leverage the {@link ng.$q + * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. + * + * The interceptors are service factories that are registered with the `$httpProvider` by + * adding them to the `$httpProvider.interceptors` array. The factory is called and + * injected with dependencies (if specified) and returns the interceptor. + * + * There are two kinds of interceptors (and two kinds of rejection interceptors): + * + * * `request`: interceptors get called with http `config` object. The function is free to + * modify the `config` or create a new one. The function needs to return the `config` + * directly or as a promise. + * * `requestError`: interceptor gets called when a previous interceptor threw an error or + * resolved with a rejection. + * * `response`: interceptors get called with http `response` object. The function is free to + * modify the `response` or create a new one. The function needs to return the `response` + * directly or as a promise. + * * `responseError`: interceptor gets called when a previous interceptor threw an error or + * resolved with a rejection. + * + * + *
      +     *   // register the interceptor as a service
      +     *   $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
      +     *     return {
      +     *       // optional method
      +     *       'request': function(config) {
      +     *         // do something on success
      +     *         return config || $q.when(config);
      +     *       },
      +     *
      +     *       // optional method
      +     *      'requestError': function(rejection) {
      +     *         // do something on error
      +     *         if (canRecover(rejection)) {
      +     *           return responseOrNewPromise
      +     *         }
      +     *         return $q.reject(rejection);
      +     *       },
      +     *
      +     *
      +     *
      +     *       // optional method
      +     *       'response': function(response) {
      +     *         // do something on success
      +     *         return response || $q.when(response);
      +     *       },
      +     *
      +     *       // optional method
      +     *      'responseError': function(rejection) {
      +     *         // do something on error
      +     *         if (canRecover(rejection)) {
      +     *           return responseOrNewPromise
      +     *         }
      +     *         return $q.reject(rejection);
      +     *       };
      +     *     }
      +     *   });
      +     *
      +     *   $httpProvider.interceptors.push('myHttpInterceptor');
      +     *
      +     *
      +     *   // register the interceptor via an anonymous factory
      +     *   $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
      +     *     return {
      +     *      'request': function(config) {
      +     *          // same as above
      +     *       },
      +     *       'response': function(response) {
      +     *          // same as above
      +     *       }
      +     *     };
      +     *   });
      +     * 
      + * + * # Response interceptors (DEPRECATED) + * + * Before you start creating interceptors, be sure to understand the + * {@link ng.$q $q and deferred/promise APIs}. + * + * For purposes of global error handling, authentication or any kind of synchronous or + * asynchronous preprocessing of received responses, it is desirable to be able to intercept + * responses for http requests before they are handed over to the application code that + * initiated these requests. The response interceptors leverage the {@link ng.$q + * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. + * + * The interceptors are service factories that are registered with the $httpProvider by + * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and + * injected with dependencies (if specified) and returns the interceptor — a function that + * takes a {@link ng.$q promise} and returns the original or a new promise. + * + *
      +     *   // register the interceptor as a service
      +     *   $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
      +     *     return function(promise) {
      +     *       return promise.then(function(response) {
      +     *         // do something on success
      +     *         return response;
      +     *       }, function(response) {
      +     *         // do something on error
      +     *         if (canRecover(response)) {
      +     *           return responseOrNewPromise
      +     *         }
      +     *         return $q.reject(response);
      +     *       });
      +     *     }
      +     *   });
      +     *
      +     *   $httpProvider.responseInterceptors.push('myHttpInterceptor');
      +     *
      +     *
      +     *   // register the interceptor via an anonymous factory
      +     *   $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
      +     *     return function(promise) {
      +     *       // same as above
      +     *     }
      +     *   });
      +     * 
      + * + * + * # Security Considerations + * + * When designing web applications, consider security threats from: + * + * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + * JSON vulnerability} + * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} + * + * Both server and the client must cooperate in order to eliminate these threats. Angular comes + * pre-configured with strategies that address these issues, but for this to work backend server + * cooperation is required. + * + * ## JSON Vulnerability Protection + * + * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + * JSON vulnerability} allows third party website to turn your JSON resource URL into + * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To + * counter this your server can prefix all JSON requests with following string `")]}',\n"`. + * Angular will automatically strip the prefix before processing it as JSON. + * + * For example if your server needs to return: + *
      +     * ['one','two']
      +     * 
      + * + * which is vulnerable to attack, your server can return: + *
      +     * )]}',
      +     * ['one','two']
      +     * 
      + * + * Angular will strip the prefix, before processing the JSON. + * + * + * ## Cross Site Request Forgery (XSRF) Protection + * + * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which + * an unauthorized site can gain your user's private data. Angular provides a mechanism + * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie + * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only + * JavaScript that runs on your domain could read the cookie, your server can be assured that + * the XHR came from JavaScript running on your domain. The header will not be set for + * cross-domain requests. + * + * To take advantage of this, your server needs to set a token in a JavaScript readable session + * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the + * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure + * that only JavaScript running on your domain could have sent the request. The token must be + * unique for each user and must be verifiable by the server (to prevent the JavaScript from + * making up its own tokens). We recommend that the token is a digest of your site's + * authentication cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} + * for added security. + * + * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName + * properties of either $httpProvider.defaults, or the per-request config object. + * + * + * @param {object} config Object describing the request to be made and how it should be + * processed. The object has following properties: + * + * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) + * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. + * - **params** – `{Object.}` – Map of strings or objects which will be turned + * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be + * JSONified. + * - **data** – `{string|Object}` – Data to be sent as the request message data. + * - **headers** – `{Object}` – Map of strings or functions which return strings representing + * HTTP headers to send to the server. If the return value of a function is null, the + * header will not be sent. + * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. + * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. + * - **transformRequest** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * - **transformResponse** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} + * that should abort the request when resolved. + * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the + * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 + * requests with credentials} for more information. + * - **responseType** - `{string}` - see {@link + * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. + * + * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the + * standard `then` method and two http specific methods: `success` and `error`. The `then` + * method takes two arguments a success and an error callback which will be called with a + * response object. The `success` and `error` methods take a single argument - a function that + * will be called when the request succeeds or fails respectively. The arguments passed into + * these functions are destructured representation of the response object passed into the + * `then` method. The response object has these properties: + * + * - **data** – `{string|Object}` – The response body transformed with the transform + * functions. + * - **status** – `{number}` – HTTP status code of the response. + * - **headers** – `{function([headerName])}` – Header getter function. + * - **config** – `{Object}` – The configuration object that was used to generate the request. + * + * @property {Array.} pendingRequests Array of config objects for currently pending + * requests. This is primarily meant to be used for debugging purposes. + * + * + * @example + + +
      + + +
      + + + +
      http status code: {{status}}
      +
      http response data: {{data}}
      +
      +
      + + function FetchCtrl($scope, $http, $templateCache) { + $scope.method = 'GET'; + $scope.url = 'http-hello.html'; + + $scope.fetch = function() { + $scope.code = null; + $scope.response = null; + + $http({method: $scope.method, url: $scope.url, cache: $templateCache}). + success(function(data, status) { + $scope.status = status; + $scope.data = data; + }). + error(function(data, status) { + $scope.data = data || "Request failed"; + $scope.status = status; + }); + }; + + $scope.updateModel = function(method, url) { + $scope.method = method; + $scope.url = url; + }; + } + + + Hello, $http! + + + it('should make an xhr GET request', function() { + element(':button:contains("Sample GET")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Hello, \$http!/); + }); + + it('should make a JSONP request to angularjs.org', function() { + element(':button:contains("Sample JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Super Hero!/); + }); + + it('should make JSONP request to invalid URL and invoke the error handler', + function() { + element(':button:contains("Invalid JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('0'); + expect(binding('data')).toBe('Request failed'); + }); + +
      + */ + function $http(requestConfig) { + var config = { + transformRequest: defaults.transformRequest, + transformResponse: defaults.transformResponse + }; + var headers = mergeHeaders(requestConfig); + + extend(config, requestConfig); + config.headers = headers; + config.method = uppercase(config.method); + + var xsrfValue = urlIsSameOrigin(config.url) + ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] + : undefined; + if (xsrfValue) { + headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; + } + + + var serverRequest = function(config) { + headers = config.headers; + var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); + + // strip content-type if data is undefined + if (isUndefined(config.data)) { + forEach(headers, function(value, header) { + if (lowercase(header) === 'content-type') { + delete headers[header]; + } + }); + } + + if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { + config.withCredentials = defaults.withCredentials; + } + + // send request + return sendReq(config, reqData, headers).then(transformResponse, transformResponse); + }; + + var chain = [serverRequest, undefined]; + var promise = $q.when(config); + + // apply interceptors + forEach(reversedInterceptors, function(interceptor) { + if (interceptor.request || interceptor.requestError) { + chain.unshift(interceptor.request, interceptor.requestError); + } + if (interceptor.response || interceptor.responseError) { + chain.push(interceptor.response, interceptor.responseError); + } + }); + + while(chain.length) { + var thenFn = chain.shift(); + var rejectFn = chain.shift(); + + promise = promise.then(thenFn, rejectFn); + } + + promise.success = function(fn) { + promise.then(function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function(fn) { + promise.then(null, function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + return promise; + + function transformResponse(response) { + // make a copy since the response must be cacheable + var resp = extend({}, response, { + data: transformData(response.data, response.headers, config.transformResponse) + }); + return (isSuccess(response.status)) + ? resp + : $q.reject(resp); + } + + function mergeHeaders(config) { + var defHeaders = defaults.headers, + reqHeaders = extend({}, config.headers), + defHeaderName, lowercaseDefHeaderName, reqHeaderName; + + defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); + + // execute if header value is function + execHeaders(defHeaders); + execHeaders(reqHeaders); + + // using for-in instead of forEach to avoid unecessary iteration after header has been found + defaultHeadersIteration: + for (defHeaderName in defHeaders) { + lowercaseDefHeaderName = lowercase(defHeaderName); + + for (reqHeaderName in reqHeaders) { + if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { + continue defaultHeadersIteration; + } + } + + reqHeaders[defHeaderName] = defHeaders[defHeaderName]; + } + + return reqHeaders; + + function execHeaders(headers) { + var headerContent; + + forEach(headers, function(headerFn, header) { + if (isFunction(headerFn)) { + headerContent = headerFn(); + if (headerContent != null) { + headers[header] = headerContent; + } else { + delete headers[header]; + } + } + }); + } + } + } + + $http.pendingRequests = []; + + /** + * @ngdoc method + * @name ng.$http#get + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `GET` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#delete + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `DELETE` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#head + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `HEAD` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#jsonp + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `JSONP` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request. + * Should contain `JSON_CALLBACK` string. + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethods('get', 'delete', 'head', 'jsonp'); + + /** + * @ngdoc method + * @name ng.$http#post + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `POST` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#put + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `PUT` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethodsWithData('post', 'put'); + + /** + * @ngdoc property + * @name ng.$http#defaults + * @propertyOf ng.$http + * + * @description + * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of + * default headers, withCredentials as well as request and response transformations. + * + * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. + */ + $http.defaults = defaults; + + + return $http; + + + function createShortMethods(names) { + forEach(arguments, function(name) { + $http[name] = function(url, config) { + return $http(extend(config || {}, { + method: name, + url: url + })); + }; + }); + } + + + function createShortMethodsWithData(name) { + forEach(arguments, function(name) { + $http[name] = function(url, data, config) { + return $http(extend(config || {}, { + method: name, + url: url, + data: data + })); + }; + }); + } + + + /** + * Makes the request. + * + * !!! ACCESSES CLOSURE VARS: + * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests + */ + function sendReq(config, reqData, reqHeaders) { + var deferred = $q.defer(), + promise = deferred.promise, + cache, + cachedResp, + url = buildUrl(config.url, config.params); + + $http.pendingRequests.push(config); + promise.then(removePendingReq, removePendingReq); + + + if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { + cache = isObject(config.cache) ? config.cache + : isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + if (cache) { + cachedResp = cache.get(url); + if (isDefined(cachedResp)) { + if (cachedResp.then) { + // cached request has already been sent, but there is no response yet + cachedResp.then(removePendingReq, removePendingReq); + return cachedResp; + } else { + // serving from cache + if (isArray(cachedResp)) { + resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2])); + } else { + resolvePromise(cachedResp, 200, {}); + } + } + } else { + // put the promise for the non-transformed response into cache as a placeholder + cache.put(url, promise); + } + } + + // if we won't have the response in cache, send the request to the backend + if (isUndefined(cachedResp)) { + $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, + config.withCredentials, config.responseType); + } + + return promise; + + + /** + * Callback registered to $httpBackend(): + * - caches the response if desired + * - resolves the raw $http promise + * - calls $apply + */ + function done(status, response, headersString) { + if (cache) { + if (isSuccess(status)) { + cache.put(url, [status, response, parseHeaders(headersString)]); + } else { + // remove promise from the cache + cache.remove(url); + } + } + + resolvePromise(response, status, headersString); + if (!$rootScope.$$phase) $rootScope.$apply(); + } + + + /** + * Resolves the raw $http promise. + */ + function resolvePromise(response, status, headers) { + // normalize internal statuses to 0 + status = Math.max(status, 0); + + (isSuccess(status) ? deferred.resolve : deferred.reject)({ + data: response, + status: status, + headers: headersGetter(headers), + config: config + }); + } + + + function removePendingReq() { + var idx = indexOf($http.pendingRequests, config); + if (idx !== -1) $http.pendingRequests.splice(idx, 1); + } + } + + + function buildUrl(url, params) { + if (!params) return url; + var parts = []; + forEachSorted(params, function(value, key) { + if (value === null || isUndefined(value)) return; + if (!isArray(value)) value = [value]; + + forEach(value, function(v) { + if (isObject(v)) { + v = toJson(v); + } + parts.push(encodeUriQuery(key) + '=' + + encodeUriQuery(v)); + }); + }); + return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); + } + + + }]; +} + +var XHR = window.XMLHttpRequest || function() { + /* global ActiveXObject */ + try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {} + try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {} + try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {} + throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); +}; + + +/** + * @ngdoc object + * @name ng.$httpBackend + * @requires $browser + * @requires $window + * @requires $document + * + * @description + * HTTP backend used by the {@link ng.$http service} that delegates to + * XMLHttpRequest object or JSONP and deals with browser incompatibilities. + * + * You should never need to use this service directly, instead use the higher-level abstractions: + * {@link ng.$http $http} or {@link ngResource.$resource $resource}. + * + * During testing this implementation is swapped with {@link ngMock.$httpBackend mock + * $httpBackend} which can be trained with responses. + */ +function $HttpBackendProvider() { + this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { + return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, + $document[0], $window.location.protocol.replace(':', '')); + }]; +} + +function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) { + // TODO(vojta): fix the signature + return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { + var status; + $browser.$$incOutstandingRequestCount(); + url = url || $browser.url(); + + if (lowercase(method) == 'jsonp') { + var callbackId = '_' + (callbacks.counter++).toString(36); + callbacks[callbackId] = function(data) { + callbacks[callbackId].data = data; + }; + + var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), + function() { + if (callbacks[callbackId].data) { + completeRequest(callback, 200, callbacks[callbackId].data); + } else { + completeRequest(callback, status || -2); + } + delete callbacks[callbackId]; + }); + } else { + var xhr = new XHR(); + xhr.open(method, url, true); + forEach(headers, function(value, key) { + if (isDefined(value)) { + xhr.setRequestHeader(key, value); + } + }); + + // In IE6 and 7, this might be called synchronously when xhr.send below is called and the + // response is in the cache. the promise api will ensure that to the app code the api is + // always async + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + var responseHeaders = xhr.getAllResponseHeaders(); + + // responseText is the old-school way of retrieving response (supported by IE8 & 9) + // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) + completeRequest(callback, + status || xhr.status, + (xhr.responseType ? xhr.response : xhr.responseText), + responseHeaders); + } + }; + + if (withCredentials) { + xhr.withCredentials = true; + } + + if (responseType) { + xhr.responseType = responseType; + } + + xhr.send(post || null); + } + + if (timeout > 0) { + var timeoutId = $browserDefer(timeoutRequest, timeout); + } else if (timeout && timeout.then) { + timeout.then(timeoutRequest); + } + + + function timeoutRequest() { + status = -1; + jsonpDone && jsonpDone(); + xhr && xhr.abort(); + } + + function completeRequest(callback, status, response, headersString) { + var protocol = locationProtocol || urlResolve(url).protocol; + + // cancel timeout and subsequent timeout promise resolution + timeoutId && $browserDefer.cancel(timeoutId); + jsonpDone = xhr = null; + + // fix status code for file protocol (it's always 0) + status = (protocol == 'file') ? (response ? 200 : 404) : status; + + // normalize IE bug (http://bugs.jquery.com/ticket/1450) + status = status == 1223 ? 204 : status; + + callback(status, response, headersString); + $browser.$$completeOutstandingRequest(noop); + } + }; + + function jsonpReq(url, done) { + // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: + // - fetches local scripts via XHR and evals them + // - adds and immediately removes script elements from the document + var script = rawDocument.createElement('script'), + doneWrapper = function() { + rawDocument.body.removeChild(script); + if (done) done(); + }; + + script.type = 'text/javascript'; + script.src = url; + + if (msie) { + script.onreadystatechange = function() { + if (/loaded|complete/.test(script.readyState)) doneWrapper(); + }; + } else { + script.onload = script.onerror = doneWrapper; + } + + rawDocument.body.appendChild(script); + return doneWrapper; + } +} + +var $interpolateMinErr = minErr('$interpolate'); + +/** + * @ngdoc object + * @name ng.$interpolateProvider + * @function + * + * @description + * + * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. + * + * @example + + + +
      + //demo.label// +
      +
      + + it('should interpolate binding with custom symbols', function() { + expect(binding('demo.label')).toBe('This binding is brought you by // interpolation symbols.'); + }); + +
      + */ +function $InterpolateProvider() { + var startSymbol = '{{'; + var endSymbol = '}}'; + + /** + * @ngdoc method + * @name ng.$interpolateProvider#startSymbol + * @methodOf ng.$interpolateProvider + * @description + * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. + * + * @param {string=} value new value to set the starting symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.startSymbol = function(value){ + if (value) { + startSymbol = value; + return this; + } else { + return startSymbol; + } + }; + + /** + * @ngdoc method + * @name ng.$interpolateProvider#endSymbol + * @methodOf ng.$interpolateProvider + * @description + * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. + * + * @param {string=} value new value to set the ending symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.endSymbol = function(value){ + if (value) { + endSymbol = value; + return this; + } else { + return endSymbol; + } + }; + + + this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { + var startSymbolLength = startSymbol.length, + endSymbolLength = endSymbol.length; + + /** + * @ngdoc function + * @name ng.$interpolate + * @function + * + * @requires $parse + * @requires $sce + * + * @description + * + * Compiles a string with markup into an interpolation function. This service is used by the + * HTML {@link ng.$compile $compile} service for data binding. See + * {@link ng.$interpolateProvider $interpolateProvider} for configuring the + * interpolation markup. + * + * +
      +         var $interpolate = ...; // injected
      +         var exp = $interpolate('Hello {{name}}!');
      +         expect(exp({name:'Angular'}).toEqual('Hello Angular!');
      +       
      + * + * + * @param {string} text The text with markup to interpolate. + * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have + * embedded expression in order to return an interpolation function. Strings with no + * embedded expression will return null for the interpolation function. + * @param {string=} trustedContext when provided, the returned function passes the interpolated + * result through {@link ng.$sce#methods_getTrusted $sce.getTrusted(interpolatedResult, + * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that + * provides Strict Contextual Escaping for details. + * @returns {function(context)} an interpolation function which is used to compute the + * interpolated string. The function has these parameters: + * + * * `context`: an object against which any expressions embedded in the strings are evaluated + * against. + * + */ + function $interpolate(text, mustHaveExpression, trustedContext) { + var startIndex, + endIndex, + index = 0, + parts = [], + length = text.length, + hasInterpolation = false, + fn, + exp, + concat = []; + + while(index < length) { + if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { + (index != startIndex) && parts.push(text.substring(index, startIndex)); + parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); + fn.exp = exp; + index = endIndex + endSymbolLength; + hasInterpolation = true; + } else { + // we did not find anything, so we have to add the remainder to the parts array + (index != length) && parts.push(text.substring(index)); + index = length; + } + } + + if (!(length = parts.length)) { + // we added, nothing, must have been an empty string. + parts.push(''); + length = 1; + } + + // Concatenating expressions makes it hard to reason about whether some combination of + // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a + // single expression be used for iframe[src], object[src], etc., we ensure that the value + // that's used is assigned or constructed by some JS code somewhere that is more testable or + // make it obvious that you bound the value to some user controlled value. This helps reduce + // the load when auditing for XSS issues. + if (trustedContext && parts.length > 1) { + throw $interpolateMinErr('noconcat', + "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + + "interpolations that concatenate multiple expressions when a trusted value is " + + "required. See http://docs.angularjs.org/api/ng.$sce", text); + } + + if (!mustHaveExpression || hasInterpolation) { + concat.length = length; + fn = function(context) { + try { + for(var i = 0, ii = length, part; i 0 && iteration >= count) { + deferred.resolve(iteration); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + } + + if (!skipApply) $rootScope.$apply(); + + }, delay); + + intervals[promise.$$intervalId] = deferred; + + return promise; + } + + + /** + * @ngdoc function + * @name ng.$interval#cancel + * @methodOf ng.$interval + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {number} promise Promise returned by the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully canceled. + */ + interval.cancel = function(promise) { + if (promise && promise.$$intervalId in intervals) { + intervals[promise.$$intervalId].reject('canceled'); + clearInterval(promise.$$intervalId); + delete intervals[promise.$$intervalId]; + return true; + } + return false; + }; + + return interval; + }]; +} + +/** + * @ngdoc object + * @name ng.$locale + * + * @description + * $locale service provides localization rules for various Angular components. As of right now the + * only public api is: + * + * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) + */ +function $LocaleProvider(){ + this.$get = function() { + return { + id: 'en-us', + + NUMBER_FORMATS: { + DECIMAL_SEP: '.', + GROUP_SEP: ',', + PATTERNS: [ + { // Decimal Pattern + minInt: 1, + minFrac: 0, + maxFrac: 3, + posPre: '', + posSuf: '', + negPre: '-', + negSuf: '', + gSize: 3, + lgSize: 3 + },{ //Currency Pattern + minInt: 1, + minFrac: 2, + maxFrac: 2, + posPre: '\u00A4', + posSuf: '', + negPre: '(\u00A4', + negSuf: ')', + gSize: 3, + lgSize: 3 + } + ], + CURRENCY_SYM: '$' + }, + + DATETIME_FORMATS: { + MONTH: + 'January,February,March,April,May,June,July,August,September,October,November,December' + .split(','), + SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), + DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), + SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), + AMPMS: ['AM','PM'], + medium: 'MMM d, y h:mm:ss a', + short: 'M/d/yy h:mm a', + fullDate: 'EEEE, MMMM d, y', + longDate: 'MMMM d, y', + mediumDate: 'MMM d, y', + shortDate: 'M/d/yy', + mediumTime: 'h:mm:ss a', + shortTime: 'h:mm a' + }, + + pluralCat: function(num) { + if (num === 1) { + return 'one'; + } + return 'other'; + } + }; + }; +} + +var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, + DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; +var $locationMinErr = minErr('$location'); + + +/** + * Encode path using encodeUriSegment, ignoring forward slashes + * + * @param {string} path Path to encode + * @returns {string} + */ +function encodePath(path) { + var segments = path.split('/'), + i = segments.length; + + while (i--) { + segments[i] = encodeUriSegment(segments[i]); + } + + return segments.join('/'); +} + +function parseAbsoluteUrl(absoluteUrl, locationObj) { + var parsedUrl = urlResolve(absoluteUrl); + + locationObj.$$protocol = parsedUrl.protocol; + locationObj.$$host = parsedUrl.hostname; + locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; +} + + +function parseAppUrl(relativeUrl, locationObj) { + var prefixed = (relativeUrl.charAt(0) !== '/'); + if (prefixed) { + relativeUrl = '/' + relativeUrl; + } + var match = urlResolve(relativeUrl); + locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? + match.pathname.substring(1) : match.pathname); + locationObj.$$search = parseKeyValue(match.search); + locationObj.$$hash = decodeURIComponent(match.hash); + + // make sure path starts with '/'; + if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { + locationObj.$$path = '/' + locationObj.$$path; + } +} + + +/** + * + * @param {string} begin + * @param {string} whole + * @returns {string} returns text from whole after begin or undefined if it does not begin with + * expected string. + */ +function beginsWith(begin, whole) { + if (whole.indexOf(begin) === 0) { + return whole.substr(begin.length); + } +} + + +function stripHash(url) { + var index = url.indexOf('#'); + return index == -1 ? url : url.substr(0, index); +} + + +function stripFile(url) { + return url.substr(0, stripHash(url).lastIndexOf('/') + 1); +} + +/* return the server only (scheme://host:port) */ +function serverBase(url) { + return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); +} + + +/** + * LocationHtml5Url represents an url + * This object is exposed as $location service when HTML5 mode is enabled and supported + * + * @constructor + * @param {string} appBase application base URL + * @param {string} basePrefix url path prefix + */ +function LocationHtml5Url(appBase, basePrefix) { + this.$$html5 = true; + basePrefix = basePrefix || ''; + var appBaseNoFile = stripFile(appBase); + parseAbsoluteUrl(appBase, this); + + + /** + * Parse given html5 (regular) url string into properties + * @param {string} newAbsoluteUrl HTML5 url + * @private + */ + this.$$parse = function(url) { + var pathUrl = beginsWith(appBaseNoFile, url); + if (!isString(pathUrl)) { + throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, + appBaseNoFile); + } + + parseAppUrl(pathUrl, this); + + if (!this.$$path) { + this.$$path = '/'; + } + + this.$$compose(); + }; + + /** + * Compose url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' + }; + + this.$$rewrite = function(url) { + var appUrl, prevAppUrl; + + if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { + prevAppUrl = appUrl; + if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { + return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); + } else { + return appBase + prevAppUrl; + } + } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { + return appBaseNoFile + appUrl; + } else if (appBaseNoFile == url + '/') { + return appBaseNoFile; + } + }; +} + + +/** + * LocationHashbangUrl represents url + * This object is exposed as $location service when developer doesn't opt into html5 mode. + * It also serves as the base class for html5 mode fallback on legacy browsers. + * + * @constructor + * @param {string} appBase application base URL + * @param {string} hashPrefix hashbang prefix + */ +function LocationHashbangUrl(appBase, hashPrefix) { + var appBaseNoFile = stripFile(appBase); + + parseAbsoluteUrl(appBase, this); + + + /** + * Parse given hashbang url into properties + * @param {string} url Hashbang url + * @private + */ + this.$$parse = function(url) { + var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); + var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' + ? beginsWith(hashPrefix, withoutBaseUrl) + : (this.$$html5) + ? withoutBaseUrl + : ''; + + if (!isString(withoutHashUrl)) { + throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, + hashPrefix); + } + parseAppUrl(withoutHashUrl, this); + this.$$compose(); + }; + + /** + * Compose hashbang url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); + }; + + this.$$rewrite = function(url) { + if(stripHash(appBase) == stripHash(url)) { + return url; + } + }; +} + + +/** + * LocationHashbangUrl represents url + * This object is exposed as $location service when html5 history api is enabled but the browser + * does not support it. + * + * @constructor + * @param {string} appBase application base URL + * @param {string} hashPrefix hashbang prefix + */ +function LocationHashbangInHtml5Url(appBase, hashPrefix) { + this.$$html5 = true; + LocationHashbangUrl.apply(this, arguments); + + var appBaseNoFile = stripFile(appBase); + + this.$$rewrite = function(url) { + var appUrl; + + if ( appBase == stripHash(url) ) { + return url; + } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { + return appBase + hashPrefix + appUrl; + } else if ( appBaseNoFile === url + '/') { + return appBaseNoFile; + } + }; +} + + +LocationHashbangInHtml5Url.prototype = + LocationHashbangUrl.prototype = + LocationHtml5Url.prototype = { + + /** + * Are we in html5 mode? + * @private + */ + $$html5: false, + + /** + * Has any change been replacing ? + * @private + */ + $$replace: false, + + /** + * @ngdoc method + * @name ng.$location#absUrl + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return full url representation with all segments encoded according to rules specified in + * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. + * + * @return {string} full url + */ + absUrl: locationGetter('$$absUrl'), + + /** + * @ngdoc method + * @name ng.$location#url + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return url (e.g. `/path?a=b#hash`) when called without any parameter. + * + * Change path, search and hash, when called with parameter and return `$location`. + * + * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) + * @param {string=} replace The path that will be changed + * @return {string} url + */ + url: function(url, replace) { + if (isUndefined(url)) + return this.$$url; + + var match = PATH_MATCH.exec(url); + if (match[1]) this.path(decodeURIComponent(match[1])); + if (match[2] || match[1]) this.search(match[3] || ''); + this.hash(match[5] || '', replace); + + return this; + }, + + /** + * @ngdoc method + * @name ng.$location#protocol + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return protocol of current url. + * + * @return {string} protocol of current url + */ + protocol: locationGetter('$$protocol'), + + /** + * @ngdoc method + * @name ng.$location#host + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return host of current url. + * + * @return {string} host of current url. + */ + host: locationGetter('$$host'), + + /** + * @ngdoc method + * @name ng.$location#port + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return port of current url. + * + * @return {Number} port + */ + port: locationGetter('$$port'), + + /** + * @ngdoc method + * @name ng.$location#path + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return path of current url when called without any parameter. + * + * Change path when called with parameter and return `$location`. + * + * Note: Path should always begin with forward slash (/), this method will add the forward slash + * if it is missing. + * + * @param {string=} path New path + * @return {string} path + */ + path: locationGetterSetter('$$path', function(path) { + return path.charAt(0) == '/' ? path : '/' + path; + }), + + /** + * @ngdoc method + * @name ng.$location#search + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return search part (as object) of current url when called without any parameter. + * + * Change search part when called with parameter and return `$location`. + * + * @param {string|Object.|Object.>} search New search params - string or + * hash object. Hash object may contain an array of values, which will be decoded as duplicates in + * the url. + * + * @param {(string|Array)=} paramValue If `search` is a string, then `paramValue` will override only a + * single search parameter. If `paramValue` is an array, it will set the parameter as a + * comma-separated value. If `paramValue` is `null`, the parameter will be deleted. + * + * @return {string} search + */ + search: function(search, paramValue) { + switch (arguments.length) { + case 0: + return this.$$search; + case 1: + if (isString(search)) { + this.$$search = parseKeyValue(search); + } else if (isObject(search)) { + this.$$search = search; + } else { + throw $locationMinErr('isrcharg', + 'The first argument of the `$location#search()` call must be a string or an object.'); + } + break; + default: + if (isUndefined(paramValue) || paramValue === null) { + delete this.$$search[search]; + } else { + this.$$search[search] = paramValue; + } + } + + this.$$compose(); + return this; + }, + + /** + * @ngdoc method + * @name ng.$location#hash + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return hash fragment when called without any parameter. + * + * Change hash fragment when called with parameter and return `$location`. + * + * @param {string=} hash New hash fragment + * @return {string} hash + */ + hash: locationGetterSetter('$$hash', identity), + + /** + * @ngdoc method + * @name ng.$location#replace + * @methodOf ng.$location + * + * @description + * If called, all changes to $location during current `$digest` will be replacing current history + * record, instead of adding new one. + */ + replace: function() { + this.$$replace = true; + return this; + } +}; + +function locationGetter(property) { + return function() { + return this[property]; + }; +} + + +function locationGetterSetter(property, preprocess) { + return function(value) { + if (isUndefined(value)) + return this[property]; + + this[property] = preprocess(value); + this.$$compose(); + + return this; + }; +} + + +/** + * @ngdoc object + * @name ng.$location + * + * @requires $browser + * @requires $sniffer + * @requires $rootElement + * + * @description + * The $location service parses the URL in the browser address bar (based on the + * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL + * available to your application. Changes to the URL in the address bar are reflected into + * $location service and changes to $location are reflected into the browser address bar. + * + * **The $location service:** + * + * - Exposes the current URL in the browser address bar, so you can + * - Watch and observe the URL. + * - Change the URL. + * - Synchronizes the URL with the browser when the user + * - Changes the address bar. + * - Clicks the back or forward button (or clicks a History link). + * - Clicks on a link. + * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). + * + * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular + * Services: Using $location} + */ + +/** + * @ngdoc object + * @name ng.$locationProvider + * @description + * Use the `$locationProvider` to configure how the application deep linking paths are stored. + */ +function $LocationProvider(){ + var hashPrefix = '', + html5Mode = false; + + /** + * @ngdoc property + * @name ng.$locationProvider#hashPrefix + * @methodOf ng.$locationProvider + * @description + * @param {string=} prefix Prefix for hash part (containing path and search) + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.hashPrefix = function(prefix) { + if (isDefined(prefix)) { + hashPrefix = prefix; + return this; + } else { + return hashPrefix; + } + }; + + /** + * @ngdoc property + * @name ng.$locationProvider#html5Mode + * @methodOf ng.$locationProvider + * @description + * @param {boolean=} mode Use HTML5 strategy if available. + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.html5Mode = function(mode) { + if (isDefined(mode)) { + html5Mode = mode; + return this; + } else { + return html5Mode; + } + }; + + /** + * @ngdoc event + * @name ng.$location#$locationChangeStart + * @eventOf ng.$location + * @eventType broadcast on root scope + * @description + * Broadcasted before a URL will change. This change can be prevented by calling + * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more + * details about event object. Upon successful change + * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + */ + + /** + * @ngdoc event + * @name ng.$location#$locationChangeSuccess + * @eventOf ng.$location + * @eventType broadcast on root scope + * @description + * Broadcasted after a URL was changed. + * + * @param {Object} angularEvent Synthetic event object. + * @param {string} newUrl New URL + * @param {string=} oldUrl URL that was before it was changed. + */ + + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', + function( $rootScope, $browser, $sniffer, $rootElement) { + var $location, + LocationMode, + baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' + initialUrl = $browser.url(), + appBase; + + if (html5Mode) { + appBase = serverBase(initialUrl) + (baseHref || '/'); + LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; + } else { + appBase = stripHash(initialUrl); + LocationMode = LocationHashbangUrl; + } + $location = new LocationMode(appBase, '#' + hashPrefix); + $location.$$parse($location.$$rewrite(initialUrl)); + + $rootElement.on('click', function(event) { + // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) + // currently we open nice url link and redirect then + + if (event.ctrlKey || event.metaKey || event.which == 2) return; + + var elm = jqLite(event.target); + + // traverse the DOM up to find first A tag + while (lowercase(elm[0].nodeName) !== 'a') { + // ignore rewriting if no A tag (reached root element, or no parent - removed from document) + if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; + } + + var absHref = elm.prop('href'); + var rewrittenUrl = $location.$$rewrite(absHref); + + if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { + event.preventDefault(); + if (rewrittenUrl != $browser.url()) { + // update location manually + $location.$$parse(rewrittenUrl); + $rootScope.$apply(); + // hack to work around FF6 bug 684208 when scenario runner clicks on links + window.angular['ff-684208-preventDefault'] = true; + } + } + }); + + + // rewrite hashbang url <> html5 url + if ($location.absUrl() != initialUrl) { + $browser.url($location.absUrl(), true); + } + + // update $location when $browser url changes + $browser.onUrlChange(function(newUrl) { + if ($location.absUrl() != newUrl) { + if ($rootScope.$broadcast('$locationChangeStart', newUrl, + $location.absUrl()).defaultPrevented) { + $browser.url($location.absUrl()); + return; + } + $rootScope.$evalAsync(function() { + var oldUrl = $location.absUrl(); + + $location.$$parse(newUrl); + afterLocationChange(oldUrl); + }); + if (!$rootScope.$$phase) $rootScope.$digest(); + } + }); + + // update browser + var changeCounter = 0; + $rootScope.$watch(function $locationWatch() { + var oldUrl = $browser.url(); + var currentReplace = $location.$$replace; + + if (!changeCounter || oldUrl != $location.absUrl()) { + changeCounter++; + $rootScope.$evalAsync(function() { + if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). + defaultPrevented) { + $location.$$parse(oldUrl); + } else { + $browser.url($location.absUrl(), currentReplace); + afterLocationChange(oldUrl); + } + }); + } + $location.$$replace = false; + + return changeCounter; + }); + + return $location; + + function afterLocationChange(oldUrl) { + $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); + } +}]; +} + +/** + * @ngdoc object + * @name ng.$log + * @requires $window + * + * @description + * Simple service for logging. Default implementation safely writes the message + * into the browser's console (if present). + * + * The main purpose of this service is to simplify debugging and troubleshooting. + * + * The default is not to log `debug` messages. You can use + * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. + * + * @example + + + function LogCtrl($scope, $log) { + $scope.$log = $log; + $scope.message = 'Hello World!'; + } + + +
      +

      Reload this page with open console, enter text and hit the log button...

      + Message: + + + + + +
      +
      +
      + */ + +/** + * @ngdoc object + * @name ng.$logProvider + * @description + * Use the `$logProvider` to configure how the application logs messages + */ +function $LogProvider(){ + var debug = true, + self = this; + + /** + * @ngdoc property + * @name ng.$logProvider#debugEnabled + * @methodOf ng.$logProvider + * @description + * @param {string=} flag enable or disable debug level messages + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.debugEnabled = function(flag) { + if (isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } + }; + + this.$get = ['$window', function($window){ + return { + /** + * @ngdoc method + * @name ng.$log#log + * @methodOf ng.$log + * + * @description + * Write a log message + */ + log: consoleLog('log'), + + /** + * @ngdoc method + * @name ng.$log#info + * @methodOf ng.$log + * + * @description + * Write an information message + */ + info: consoleLog('info'), + + /** + * @ngdoc method + * @name ng.$log#warn + * @methodOf ng.$log + * + * @description + * Write a warning message + */ + warn: consoleLog('warn'), + + /** + * @ngdoc method + * @name ng.$log#error + * @methodOf ng.$log + * + * @description + * Write an error message + */ + error: consoleLog('error'), + + /** + * @ngdoc method + * @name ng.$log#debug + * @methodOf ng.$log + * + * @description + * Write a debug message + */ + debug: (function () { + var fn = consoleLog('debug'); + + return function() { + if (debug) { + fn.apply(self, arguments); + } + }; + }()) + }; + + function formatError(arg) { + if (arg instanceof Error) { + if (arg.stack) { + arg = (arg.message && arg.stack.indexOf(arg.message) === -1) + ? 'Error: ' + arg.message + '\n' + arg.stack + : arg.stack; + } else if (arg.sourceURL) { + arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; + } + } + return arg; + } + + function consoleLog(type) { + var console = $window.console || {}, + logFn = console[type] || console.log || noop; + + if (logFn.apply) { + return function() { + var args = []; + forEach(arguments, function(arg) { + args.push(formatError(arg)); + }); + return logFn.apply(console, args); + }; + } + + // we are IE which either doesn't have window.console => this is noop and we do nothing, + // or we are IE where console.log doesn't have apply so we log at least first 2 args + return function(arg1, arg2) { + logFn(arg1, arg2 == null ? '' : arg2); + }; + } + }]; +} + +var $parseMinErr = minErr('$parse'); +var promiseWarningCache = {}; +var promiseWarning; + +// Sandboxing Angular Expressions +// ------------------------------ +// Angular expressions are generally considered safe because these expressions only have direct +// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by +// obtaining a reference to native JS functions such as the Function constructor, the global Window +// or Document object. In addition, many powerful functions for use by JavaScript code are +// published on scope that shouldn't be available from within an Angular expression. +// +// As an example, consider the following Angular expression: +// +// {}.toString.constructor(alert("evil JS code")) +// +// We want to prevent this type of access. For the sake of performance, during the lexing phase we +// disallow any "dotted" access to any member named "constructor" or to any member whose name begins +// or ends with an underscore. The latter allows one to exclude the private / JavaScript only API +// available on the scope and controllers from the context of an Angular expression. +// +// For reflective calls (a[b]), we check that the value of the lookup is not the Function +// constructor, Window or DOM node while evaluating the expression, which is a stronger but more +// expensive test. Since reflective calls are expensive anyway, this is not such a big deal compared +// to static dereferencing. +// +// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits +// against the expression language, but not to prevent exploits that were enabled by exposing +// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good +// practice and therefore we are not even trying to protect against interaction with an object +// explicitly exposed in this way. +// +// A developer could foil the name check by aliasing the Function constructor under a different +// name on the scope. +// +// In general, it is not possible to access a Window object from an angular expression unless a +// window or some DOM object that has a reference to window is published onto a Scope. + +function ensureSafeMemberName(name, fullExpression, allowConstructor) { + if (typeof name !== 'string' && toString.apply(name) !== "[object String]") { + return name; + } + if (name === "constructor" && !allowConstructor) { + throw $parseMinErr('isecfld', + 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + if (name.charAt(0) === '_' || name.charAt(name.length-1) === '_') { + throw $parseMinErr('isecprv', + 'Referencing private fields in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } + return name; +} + +function ensureSafeObject(obj, fullExpression) { + // nifty check if obj is Function that is fast and works across iframes and other contexts + if (obj && obj.constructor === obj) { + throw $parseMinErr('isecfn', + 'Referencing Function in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isWindow(obj) + obj && obj.document && obj.location && obj.alert && obj.setInterval) { + throw $parseMinErr('isecwindow', + 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else if (// isElement(obj) + obj && (obj.nodeName || (obj.on && obj.find))) { + throw $parseMinErr('isecdom', + 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', + fullExpression); + } else { + return obj; + } +} + +var OPERATORS = { + /* jshint bitwise : false */ + 'null':function(){return null;}, + 'true':function(){return true;}, + 'false':function(){return false;}, + undefined:noop, + '+':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + if (isDefined(a)) { + if (isDefined(b)) { + return a + b; + } + return a; + } + return isDefined(b)?b:undefined;}, + '-':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + return (isDefined(a)?a:0)-(isDefined(b)?b:0); + }, + '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, + '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, + '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, + '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, + '=':noop, + '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, + '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, + '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, + '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, + '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, + '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, + '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, + '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, + '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, + '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, +// '|':function(self, locals, a,b){return a|b;}, + '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, + '!':function(self, locals, a){return !a(self, locals);} +}; +/* jshint bitwise: true */ +var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; + + +///////////////////////////////////////// + + +/** + * @constructor + */ +var Lexer = function (options) { + this.options = options; +}; + +Lexer.prototype = { + constructor: Lexer, + + lex: function (text) { + this.text = text; + + this.index = 0; + this.ch = undefined; + this.lastCh = ':'; // can start regexp + + this.tokens = []; + + var token; + var json = []; + + while (this.index < this.text.length) { + this.ch = this.text.charAt(this.index); + if (this.is('"\'')) { + this.readString(this.ch); + } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { + this.readNumber(); + } else if (this.isIdent(this.ch)) { + this.readIdent(); + // identifiers can only be if the preceding char was a { or , + if (this.was('{,') && json[0] === '{' && + (token = this.tokens[this.tokens.length - 1])) { + token.json = token.text.indexOf('.') === -1; + } + } else if (this.is('(){}[].,;:?')) { + this.tokens.push({ + index: this.index, + text: this.ch, + json: (this.was(':[,') && this.is('{[')) || this.is('}]:,') + }); + if (this.is('{[')) json.unshift(this.ch); + if (this.is('}]')) json.shift(); + this.index++; + } else if (this.isWhitespace(this.ch)) { + this.index++; + continue; + } else { + var ch2 = this.ch + this.peek(); + var ch3 = ch2 + this.peek(2); + var fn = OPERATORS[this.ch]; + var fn2 = OPERATORS[ch2]; + var fn3 = OPERATORS[ch3]; + if (fn3) { + this.tokens.push({index: this.index, text: ch3, fn: fn3}); + this.index += 3; + } else if (fn2) { + this.tokens.push({index: this.index, text: ch2, fn: fn2}); + this.index += 2; + } else if (fn) { + this.tokens.push({ + index: this.index, + text: this.ch, + fn: fn, + json: (this.was('[,:') && this.is('+-')) + }); + this.index += 1; + } else { + this.throwError('Unexpected next character ', this.index, this.index + 1); + } + } + this.lastCh = this.ch; + } + return this.tokens; + }, + + is: function(chars) { + return chars.indexOf(this.ch) !== -1; + }, + + was: function(chars) { + return chars.indexOf(this.lastCh) !== -1; + }, + + peek: function(i) { + var num = i || 1; + return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; + }, + + isNumber: function(ch) { + return ('0' <= ch && ch <= '9'); + }, + + isWhitespace: function(ch) { + // IE treats non-breaking space as \u00A0 + return (ch === ' ' || ch === '\r' || ch === '\t' || + ch === '\n' || ch === '\v' || ch === '\u00A0'); + }, + + isIdent: function(ch) { + return ('a' <= ch && ch <= 'z' || + 'A' <= ch && ch <= 'Z' || + '_' === ch || ch === '$'); + }, + + isExpOperator: function(ch) { + return (ch === '-' || ch === '+' || this.isNumber(ch)); + }, + + throwError: function(error, start, end) { + end = end || this.index; + var colStr = (isDefined(start) + ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' + : ' ' + end); + throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', + error, colStr, this.text); + }, + + readNumber: function() { + var number = ''; + var start = this.index; + while (this.index < this.text.length) { + var ch = lowercase(this.text.charAt(this.index)); + if (ch == '.' || this.isNumber(ch)) { + number += ch; + } else { + var peekCh = this.peek(); + if (ch == 'e' && this.isExpOperator(peekCh)) { + number += ch; + } else if (this.isExpOperator(ch) && + peekCh && this.isNumber(peekCh) && + number.charAt(number.length - 1) == 'e') { + number += ch; + } else if (this.isExpOperator(ch) && + (!peekCh || !this.isNumber(peekCh)) && + number.charAt(number.length - 1) == 'e') { + this.throwError('Invalid exponent'); + } else { + break; + } + } + this.index++; + } + number = 1 * number; + this.tokens.push({ + index: start, + text: number, + json: true, + fn: function() { return number; } + }); + }, + + readIdent: function() { + var parser = this; + + var ident = ''; + var start = this.index; + + var lastDot, peekIndex, methodName, ch; + + while (this.index < this.text.length) { + ch = this.text.charAt(this.index); + if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { + if (ch === '.') lastDot = this.index; + ident += ch; + } else { + break; + } + this.index++; + } + + //check if this is not a method invocation and if it is back out to last dot + if (lastDot) { + peekIndex = this.index; + while (peekIndex < this.text.length) { + ch = this.text.charAt(peekIndex); + if (ch === '(') { + methodName = ident.substr(lastDot - start + 1); + ident = ident.substr(0, lastDot - start); + this.index = peekIndex; + break; + } + if (this.isWhitespace(ch)) { + peekIndex++; + } else { + break; + } + } + } + + + var token = { + index: start, + text: ident + }; + + // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn + if (OPERATORS.hasOwnProperty(ident)) { + token.fn = OPERATORS[ident]; + token.json = OPERATORS[ident]; + } else { + var getter = getterFn(ident, this.options, this.text); + token.fn = extend(function(self, locals) { + return (getter(self, locals)); + }, { + assign: function(self, value) { + return setter(self, ident, value, parser.text, parser.options); + } + }); + } + + this.tokens.push(token); + + if (methodName) { + this.tokens.push({ + index:lastDot, + text: '.', + json: false + }); + this.tokens.push({ + index: lastDot + 1, + text: methodName, + json: false + }); + } + }, + + readString: function(quote) { + var start = this.index; + this.index++; + var string = ''; + var rawString = quote; + var escape = false; + while (this.index < this.text.length) { + var ch = this.text.charAt(this.index); + rawString += ch; + if (escape) { + if (ch === 'u') { + var hex = this.text.substring(this.index + 1, this.index + 5); + if (!hex.match(/[\da-f]{4}/i)) + this.throwError('Invalid unicode escape [\\u' + hex + ']'); + this.index += 4; + string += String.fromCharCode(parseInt(hex, 16)); + } else { + var rep = ESCAPE[ch]; + if (rep) { + string += rep; + } else { + string += ch; + } + } + escape = false; + } else if (ch === '\\') { + escape = true; + } else if (ch === quote) { + this.index++; + this.tokens.push({ + index: start, + text: rawString, + string: string, + json: true, + fn: function() { return string; } + }); + return; + } else { + string += ch; + } + this.index++; + } + this.throwError('Unterminated quote', start); + } +}; + + +/** + * @constructor + */ +var Parser = function (lexer, $filter, options) { + this.lexer = lexer; + this.$filter = $filter; + this.options = options; +}; + +Parser.ZERO = function () { return 0; }; + +Parser.prototype = { + constructor: Parser, + + parse: function (text, json) { + this.text = text; + + //TODO(i): strip all the obsolte json stuff from this file + this.json = json; + + this.tokens = this.lexer.lex(text); + + if (json) { + // The extra level of aliasing is here, just in case the lexer misses something, so that + // we prevent any accidental execution in JSON. + this.assignment = this.logicalOR; + + this.functionCall = + this.fieldAccess = + this.objectIndex = + this.filterChain = function() { + this.throwError('is not valid json', {text: text, index: 0}); + }; + } + + var value = json ? this.primary() : this.statements(); + + if (this.tokens.length !== 0) { + this.throwError('is an unexpected token', this.tokens[0]); + } + + value.literal = !!value.literal; + value.constant = !!value.constant; + + return value; + }, + + primary: function () { + var primary; + if (this.expect('(')) { + primary = this.filterChain(); + this.consume(')'); + } else if (this.expect('[')) { + primary = this.arrayDeclaration(); + } else if (this.expect('{')) { + primary = this.object(); + } else { + var token = this.expect(); + primary = token.fn; + if (!primary) { + this.throwError('not a primary expression', token); + } + if (token.json) { + primary.constant = true; + primary.literal = true; + } + } + + var next, context; + while ((next = this.expect('(', '[', '.'))) { + if (next.text === '(') { + primary = this.functionCall(primary, context); + context = null; + } else if (next.text === '[') { + context = primary; + primary = this.objectIndex(primary); + } else if (next.text === '.') { + context = primary; + primary = this.fieldAccess(primary); + } else { + this.throwError('IMPOSSIBLE'); + } + } + return primary; + }, + + throwError: function(msg, token) { + throw $parseMinErr('syntax', + 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', + token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); + }, + + peekToken: function() { + if (this.tokens.length === 0) + throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); + return this.tokens[0]; + }, + + peek: function(e1, e2, e3, e4) { + if (this.tokens.length > 0) { + var token = this.tokens[0]; + var t = token.text; + if (t === e1 || t === e2 || t === e3 || t === e4 || + (!e1 && !e2 && !e3 && !e4)) { + return token; + } + } + return false; + }, + + expect: function(e1, e2, e3, e4){ + var token = this.peek(e1, e2, e3, e4); + if (token) { + if (this.json && !token.json) { + this.throwError('is not valid json', token); + } + this.tokens.shift(); + return token; + } + return false; + }, + + consume: function(e1){ + if (!this.expect(e1)) { + this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); + } + }, + + unaryFn: function(fn, right) { + return extend(function(self, locals) { + return fn(self, locals, right); + }, { + constant:right.constant + }); + }, + + ternaryFn: function(left, middle, right){ + return extend(function(self, locals){ + return left(self, locals) ? middle(self, locals) : right(self, locals); + }, { + constant: left.constant && middle.constant && right.constant + }); + }, + + binaryFn: function(left, fn, right) { + return extend(function(self, locals) { + return fn(self, locals, left, right); + }, { + constant:left.constant && right.constant + }); + }, + + statements: function() { + var statements = []; + while (true) { + if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) + statements.push(this.filterChain()); + if (!this.expect(';')) { + // optimize for the common case where there is only one statement. + // TODO(size): maybe we should not support multiple statements? + return (statements.length === 1) + ? statements[0] + : function(self, locals) { + var value; + for (var i = 0; i < statements.length; i++) { + var statement = statements[i]; + if (statement) { + value = statement(self, locals); + } + } + return value; + }; + } + } + }, + + filterChain: function() { + var left = this.expression(); + var token; + while (true) { + if ((token = this.expect('|'))) { + left = this.binaryFn(left, token.fn, this.filter()); + } else { + return left; + } + } + }, + + filter: function() { + var token = this.expect(); + var fn = this.$filter(token.text); + var argsFn = []; + while (true) { + if ((token = this.expect(':'))) { + argsFn.push(this.expression()); + } else { + var fnInvoke = function(self, locals, input) { + var args = [input]; + for (var i = 0; i < argsFn.length; i++) { + args.push(argsFn[i](self, locals)); + } + return fn.apply(self, args); + }; + return function() { + return fnInvoke; + }; + } + } + }, + + expression: function() { + return this.assignment(); + }, + + assignment: function() { + var left = this.ternary(); + var right; + var token; + if ((token = this.expect('='))) { + if (!left.assign) { + this.throwError('implies assignment but [' + + this.text.substring(0, token.index) + '] can not be assigned to', token); + } + right = this.ternary(); + return function(scope, locals) { + return left.assign(scope, right(scope, locals), locals); + }; + } + return left; + }, + + ternary: function() { + var left = this.logicalOR(); + var middle; + var token; + if ((token = this.expect('?'))) { + middle = this.ternary(); + if ((token = this.expect(':'))) { + return this.ternaryFn(left, middle, this.ternary()); + } else { + this.throwError('expected :', token); + } + } else { + return left; + } + }, + + logicalOR: function() { + var left = this.logicalAND(); + var token; + while (true) { + if ((token = this.expect('||'))) { + left = this.binaryFn(left, token.fn, this.logicalAND()); + } else { + return left; + } + } + }, + + logicalAND: function() { + var left = this.equality(); + var token; + if ((token = this.expect('&&'))) { + left = this.binaryFn(left, token.fn, this.logicalAND()); + } + return left; + }, + + equality: function() { + var left = this.relational(); + var token; + if ((token = this.expect('==','!=','===','!=='))) { + left = this.binaryFn(left, token.fn, this.equality()); + } + return left; + }, + + relational: function() { + var left = this.additive(); + var token; + if ((token = this.expect('<', '>', '<=', '>='))) { + left = this.binaryFn(left, token.fn, this.relational()); + } + return left; + }, + + additive: function() { + var left = this.multiplicative(); + var token; + while ((token = this.expect('+','-'))) { + left = this.binaryFn(left, token.fn, this.multiplicative()); + } + return left; + }, + + multiplicative: function() { + var left = this.unary(); + var token; + while ((token = this.expect('*','/','%'))) { + left = this.binaryFn(left, token.fn, this.unary()); + } + return left; + }, + + unary: function() { + var token; + if (this.expect('+')) { + return this.primary(); + } else if ((token = this.expect('-'))) { + return this.binaryFn(Parser.ZERO, token.fn, this.unary()); + } else if ((token = this.expect('!'))) { + return this.unaryFn(token.fn, this.unary()); + } else { + return this.primary(); + } + }, + + fieldAccess: function(object) { + var parser = this; + var field = this.expect().text; + var getter = getterFn(field, this.options, this.text); + + return extend(function(scope, locals, self) { + return getter(self || object(scope, locals), locals); + }, { + assign: function(scope, value, locals) { + return setter(object(scope, locals), field, value, parser.text, parser.options); + } + }); + }, + + objectIndex: function(obj) { + var parser = this; + + var indexFn = this.expression(); + this.consume(']'); + + return extend(function(self, locals) { + var o = obj(self, locals), + // In the getter, we will not block looking up "constructor" by name in order to support user defined + // constructors. However, if value looked up is the Function constructor, we will still block it in the + // ensureSafeObject call right after we look up o[i] (a few lines below.) + i = ensureSafeMemberName(indexFn(self, locals), parser.text, true /* allowConstructor */), + v, p; + + if (!o) return undefined; + v = ensureSafeObject(o[i], parser.text); + if (v && v.then && parser.options.unwrapPromises) { + p = v; + if (!('$$v' in v)) { + p.$$v = undefined; + p.then(function(val) { p.$$v = val; }); + } + v = v.$$v; + } + return v; + }, { + assign: function(self, value, locals) { + var key = ensureSafeMemberName(indexFn(self, locals), parser.text); + // prevent overwriting of Function.constructor which would break ensureSafeObject check + var safe = ensureSafeObject(obj(self, locals), parser.text); + return safe[key] = value; + } + }); + }, + + functionCall: function(fn, contextGetter) { + var argsFn = []; + if (this.peekToken().text !== ')') { + do { + argsFn.push(this.expression()); + } while (this.expect(',')); + } + this.consume(')'); + + var parser = this; + + return function(scope, locals) { + var args = []; + var context = contextGetter ? contextGetter(scope, locals) : scope; + + for (var i = 0; i < argsFn.length; i++) { + args.push(argsFn[i](scope, locals)); + } + var fnPtr = fn(scope, locals, context) || noop; + + ensureSafeObject(context, parser.text); + ensureSafeObject(fnPtr, parser.text); + + // IE stupidity! (IE doesn't have apply for some native functions) + var v = fnPtr.apply + ? fnPtr.apply(context, args) + : fnPtr(args[0], args[1], args[2], args[3], args[4]); + + return ensureSafeObject(v, parser.text); + }; + }, + + // This is used with json array declaration + arrayDeclaration: function () { + var elementFns = []; + var allConstant = true; + if (this.peekToken().text !== ']') { + do { + var elementFn = this.expression(); + elementFns.push(elementFn); + if (!elementFn.constant) { + allConstant = false; + } + } while (this.expect(',')); + } + this.consume(']'); + + return extend(function(self, locals) { + var array = []; + for (var i = 0; i < elementFns.length; i++) { + array.push(elementFns[i](self, locals)); + } + return array; + }, { + literal: true, + constant: allConstant + }); + }, + + object: function () { + var keyValues = []; + var allConstant = true; + if (this.peekToken().text !== '}') { + do { + var token = this.expect(), + key = token.string || token.text; + this.consume(':'); + var value = this.expression(); + keyValues.push({key: key, value: value}); + if (!value.constant) { + allConstant = false; + } + } while (this.expect(',')); + } + this.consume('}'); + + return extend(function(self, locals) { + var object = {}; + for (var i = 0; i < keyValues.length; i++) { + var keyValue = keyValues[i]; + object[keyValue.key] = keyValue.value(self, locals); + } + return object; + }, { + literal: true, + constant: allConstant + }); + } +}; + + +////////////////////////////////////////////////// +// Parser helper functions +////////////////////////////////////////////////// + +function setter(obj, path, setValue, fullExp, options) { + //needed? + options = options || {}; + + var element = path.split('.'), key; + for (var i = 0; element.length > 1; i++) { + key = ensureSafeMemberName(element.shift(), fullExp); + var propertyObj = obj[key]; + if (!propertyObj) { + propertyObj = {}; + obj[key] = propertyObj; + } + obj = propertyObj; + if (obj.then && options.unwrapPromises) { + promiseWarning(fullExp); + if (!("$$v" in obj)) { + (function(promise) { + promise.then(function(val) { promise.$$v = val; }); } + )(obj); + } + if (obj.$$v === undefined) { + obj.$$v = {}; + } + obj = obj.$$v; + } + } + key = ensureSafeMemberName(element.shift(), fullExp); + obj[key] = setValue; + return setValue; +} + +var getterFnCache = {}; + +/** + * Implementation of the "Black Hole" variant from: + * - http://jsperf.com/angularjs-parse-getter/4 + * - http://jsperf.com/path-evaluation-simplified/7 + */ +function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { + ensureSafeMemberName(key0, fullExp); + ensureSafeMemberName(key1, fullExp); + ensureSafeMemberName(key2, fullExp); + ensureSafeMemberName(key3, fullExp); + ensureSafeMemberName(key4, fullExp); + + return !options.unwrapPromises + ? function cspSafeGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; + + if (pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key0]; + + if (!key1 || pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key1]; + + if (!key2 || pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key2]; + + if (!key3 || pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key3]; + + if (!key4 || pathVal === null || pathVal === undefined) return pathVal; + pathVal = pathVal[key4]; + + return pathVal; + } + : function cspSafePromiseEnabledGetter(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, + promise; + + if (pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key0]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key1 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key1]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key2 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key2]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key3 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key3]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key4 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key4]; + if (pathVal && pathVal.then) { + promiseWarning(fullExp); + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + return pathVal; + }; +} + +function getterFn(path, options, fullExp) { + // Check whether the cache has this getter already. + // We can use hasOwnProperty directly on the cache because we ensure, + // see below, that the cache never stores a path called 'hasOwnProperty' + if (getterFnCache.hasOwnProperty(path)) { + return getterFnCache[path]; + } + + var pathKeys = path.split('.'), + pathKeysLength = pathKeys.length, + fn; + + if (options.csp) { + if (pathKeysLength < 6) { + fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, + options); + } else { + fn = function(scope, locals) { + var i = 0, val; + do { + val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], + pathKeys[i++], fullExp, options)(scope, locals); + + locals = undefined; // clear after first iteration + scope = val; + } while (i < pathKeysLength); + return val; + }; + } + } else { + var code = 'var l, fn, p;\n'; + forEach(pathKeys, function(key, index) { + ensureSafeMemberName(key, fullExp); + code += 'if(s === null || s === undefined) return s;\n' + + 'l=s;\n' + + 's='+ (index + // we simply dereference 's' on any .dot notation + ? 's' + // but if we are first then we check locals first, and if so read it first + : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + + (options.unwrapPromises + ? 'if (s && s.then) {\n' + + ' pw("' + fullExp.replace(/\"/g, '\\"') + '");\n' + + ' if (!("$$v" in s)) {\n' + + ' p=s;\n' + + ' p.$$v = undefined;\n' + + ' p.then(function(v) {p.$$v=v;});\n' + + '}\n' + + ' s=s.$$v\n' + + '}\n' + : ''); + }); + code += 'return s;'; + + /* jshint -W054 */ + var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning + /* jshint +W054 */ + evaledFnGetter.toString = function() { return code; }; + fn = function(scope, locals) { + return evaledFnGetter(scope, locals, promiseWarning); + }; + } + + // Only cache the value if it's not going to mess up the cache object + // This is more performant that using Object.prototype.hasOwnProperty.call + if (path !== 'hasOwnProperty') { + getterFnCache[path] = fn; + } + return fn; +} + +/////////////////////////////////// + +/** + * @ngdoc function + * @name ng.$parse + * @function + * + * @description + * + * Converts Angular {@link guide/expression expression} into a function. + * + *
      + *   var getter = $parse('user.name');
      + *   var setter = getter.assign;
      + *   var context = {user:{name:'angular'}};
      + *   var locals = {user:{name:'local'}};
      + *
      + *   expect(getter(context)).toEqual('angular');
      + *   setter(context, 'newValue');
      + *   expect(context.user.name).toEqual('newValue');
      + *   expect(getter(context, locals)).toEqual('local');
      + * 
      + * + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + * + * The returned function also has the following properties: + * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript + * literal. + * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript + * constant literals. + * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be + * set to a function to change its value on the given context. + * + */ + + +/** + * @ngdoc object + * @name ng.$parseProvider + * @function + * + * @description + * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} + * service. + */ +function $ParseProvider() { + var cache = {}; + + var $parseOptions = { + csp: false, + unwrapPromises: false, + logPromiseWarnings: true + }; + + + /** + * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. + * + * @ngdoc method + * @name ng.$parseProvider#unwrapPromises + * @methodOf ng.$parseProvider + * @description + * + * **This feature is deprecated, see deprecation notes below for more info** + * + * If set to true (default is false), $parse will unwrap promises automatically when a promise is + * found at any part of the expression. In other words, if set to true, the expression will always + * result in a non-promise value. + * + * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, + * the fulfillment value is used in place of the promise while evaluating the expression. + * + * **Deprecation notice** + * + * This is a feature that didn't prove to be wildly useful or popular, primarily because of the + * dichotomy between data access in templates (accessed as raw values) and controller code + * (accessed as promises). + * + * In most code we ended up resolving promises manually in controllers anyway and thus unifying + * the model access there. + * + * Other downsides of automatic promise unwrapping: + * + * - when building components it's often desirable to receive the raw promises + * - adds complexity and slows down expression evaluation + * - makes expression code pre-generation unattractive due to the amount of code that needs to be + * generated + * - makes IDE auto-completion and tool support hard + * + * **Warning Logs** + * + * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a + * promise (to reduce the noise, each expression is logged only once). To disable this logging use + * `$parseProvider.logPromiseWarnings(false)` api. + * + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as getter and self if used as + * setter. + */ + this.unwrapPromises = function(value) { + if (isDefined(value)) { + $parseOptions.unwrapPromises = !!value; + return this; + } else { + return $parseOptions.unwrapPromises; + } + }; + + + /** + * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. + * + * @ngdoc method + * @name ng.$parseProvider#logPromiseWarnings + * @methodOf ng.$parseProvider + * @description + * + * Controls whether Angular should log a warning on any encounter of a promise in an expression. + * + * The default is set to `true`. + * + * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as getter and self if used as + * setter. + */ + this.logPromiseWarnings = function(value) { + if (isDefined(value)) { + $parseOptions.logPromiseWarnings = value; + return this; + } else { + return $parseOptions.logPromiseWarnings; + } + }; + + + this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { + $parseOptions.csp = $sniffer.csp; + + promiseWarning = function promiseWarningFn(fullExp) { + if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; + promiseWarningCache[fullExp] = true; + $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + + 'Automatic unwrapping of promises in Angular expressions is deprecated.'); + }; + + return function(exp) { + var parsedExpression; + + switch (typeof exp) { + case 'string': + + if (cache.hasOwnProperty(exp)) { + return cache[exp]; + } + + var lexer = new Lexer($parseOptions); + var parser = new Parser(lexer, $filter, $parseOptions); + parsedExpression = parser.parse(exp, false); + + if (exp !== 'hasOwnProperty') { + // Only cache the value if it's not going to mess up the cache object + // This is more performant that using Object.prototype.hasOwnProperty.call + cache[exp] = parsedExpression; + } + + return parsedExpression; + + case 'function': + return exp; + + default: + return noop; + } + }; + }]; +} + +/** + * @ngdoc service + * @name ng.$q + * @requires $rootScope + * + * @description + * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * + * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an + * interface for interacting with an object that represents the result of an action that is + * performed asynchronously, and may or may not be finished at any given point in time. + * + * From the perspective of dealing with error handling, deferred and promise APIs are to + * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. + * + *
      + *   // for the purpose of this example let's assume that variables `$q` and `scope` are
      + *   // available in the current lexical scope (they could have been injected or passed in).
      + *
      + *   function asyncGreet(name) {
      + *     var deferred = $q.defer();
      + *
      + *     setTimeout(function() {
      + *       // since this fn executes async in a future turn of the event loop, we need to wrap
      + *       // our code into an $apply call so that the model changes are properly observed.
      + *       scope.$apply(function() {
      + *         deferred.notify('About to greet ' + name + '.');
      + *
      + *         if (okToGreet(name)) {
      + *           deferred.resolve('Hello, ' + name + '!');
      + *         } else {
      + *           deferred.reject('Greeting ' + name + ' is not allowed.');
      + *         }
      + *       });
      + *     }, 1000);
      + *
      + *     return deferred.promise;
      + *   }
      + *
      + *   var promise = asyncGreet('Robin Hood');
      + *   promise.then(function(greeting) {
      + *     alert('Success: ' + greeting);
      + *   }, function(reason) {
      + *     alert('Failed: ' + reason);
      + *   }, function(update) {
      + *     alert('Got notification: ' + update);
      + *   });
      + * 
      + * + * At first it might not be obvious why this extra complexity is worth the trouble. The payoff + * comes in the way of guarantees that promise and deferred APIs make, see + * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. + * + * Additionally the promise api allows for composition that is very hard to do with the + * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. + * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the + * section on serial or parallel joining of promises. + * + * + * # The Deferred API + * + * A new instance of deferred is constructed by calling `$q.defer()`. + * + * The purpose of the deferred object is to expose the associated Promise instance as well as APIs + * that can be used for signaling the successful or unsuccessful completion, as well as the status + * of the task. + * + * **Methods** + * + * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection + * constructed via `$q.reject`, the promise will be rejected instead. + * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to + * resolving it with a rejection constructed via `$q.reject`. + * - `notify(value)` - provides updates on the status of the promises execution. This may be called + * multiple times before the promise is either resolved or rejected. + * + * **Properties** + * + * - promise – `{Promise}` – promise object associated with this deferred. + * + * + * # The Promise API + * + * A new promise instance is created when a deferred instance is created and can be retrieved by + * calling `deferred.promise`. + * + * The purpose of the promise object is to allow for interested parties to get access to the result + * of the deferred task when it completes. + * + * **Methods** + * + * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or + * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously + * as soon as the result is available. The callbacks are called with a single argument: the result + * or rejection reason. Additionally, the notify callback may be called zero or more times to + * provide a progress indication, before the promise is resolved or rejected. + * + * This method *returns a new promise* which is resolved or rejected via the return value of the + * `successCallback`, `errorCallback`. It also notifies via the return value of the + * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback + * method. + * + * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` + * + * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, + * but to do so without modifying the final value. This is useful to release resources or do some + * clean-up that needs to be done whether the promise was rejected or resolved. See the [full + * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for + * more information. + * + * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as + * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to + * make your code IE8 compatible. + * + * # Chaining promises + * + * Because calling the `then` method of a promise returns a new derived promise, it is easily + * possible to create a chain of promises: + * + *
      + *   promiseB = promiseA.then(function(result) {
      + *     return result + 1;
      + *   });
      + *
      + *   // promiseB will be resolved immediately after promiseA is resolved and its value
      + *   // will be the result of promiseA incremented by 1
      + * 
      + * + * It is possible to create chains of any length and since a promise can be resolved with another + * promise (which will defer its resolution further), it is possible to pause/defer resolution of + * the promises at any point in the chain. This makes it possible to implement powerful APIs like + * $http's response interceptors. + * + * + * # Differences between Kris Kowal's Q and $q + * + * There are three main differences: + * + * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation + * mechanism in angular, which means faster propagation of resolution or rejection into your + * models and avoiding unnecessary browser repaints, which would result in flickering UI. + * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains + * all the important functionality needed for common async tasks. + * + * # Testing + * + *
      + *    it('should simulate promise', inject(function($q, $rootScope) {
      + *      var deferred = $q.defer();
      + *      var promise = deferred.promise;
      + *      var resolvedValue;
      + *
      + *      promise.then(function(value) { resolvedValue = value; });
      + *      expect(resolvedValue).toBeUndefined();
      + *
      + *      // Simulate resolving of promise
      + *      deferred.resolve(123);
      + *      // Note that the 'then' function does not get called synchronously.
      + *      // This is because we want the promise API to always be async, whether or not
      + *      // it got called synchronously or asynchronously.
      + *      expect(resolvedValue).toBeUndefined();
      + *
      + *      // Propagate promise resolution to 'then' functions using $apply().
      + *      $rootScope.$apply();
      + *      expect(resolvedValue).toEqual(123);
      + *    });
      + *  
      + */ +function $QProvider() { + + this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { + return qFactory(function(callback) { + $rootScope.$evalAsync(callback); + }, $exceptionHandler); + }]; +} + + +/** + * Constructs a promise manager. + * + * @param {function(function)} nextTick Function for executing functions in the next turn. + * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for + * debugging purposes. + * @returns {object} Promise manager. + */ +function qFactory(nextTick, exceptionHandler) { + + /** + * @ngdoc + * @name ng.$q#defer + * @methodOf ng.$q + * @description + * Creates a `Deferred` object which represents a task which will finish in the future. + * + * @returns {Deferred} Returns a new instance of deferred. + */ + var defer = function() { + var pending = [], + value, deferred; + + deferred = { + + resolve: function(val) { + if (pending) { + var callbacks = pending; + pending = undefined; + value = ref(val); + + if (callbacks.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + value.then(callback[0], callback[1], callback[2]); + } + }); + } + } + }, + + + reject: function(reason) { + deferred.resolve(reject(reason)); + }, + + + notify: function(progress) { + if (pending) { + var callbacks = pending; + + if (pending.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + callback[2](progress); + } + }); + } + } + }, + + + promise: { + then: function(callback, errback, progressback) { + var result = defer(); + + var wrappedCallback = function(value) { + try { + result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; + + var wrappedErrback = function(reason) { + try { + result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; + + var wrappedProgressback = function(progress) { + try { + result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); + } catch(e) { + exceptionHandler(e); + } + }; + + if (pending) { + pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); + } else { + value.then(wrappedCallback, wrappedErrback, wrappedProgressback); + } + + return result.promise; + }, + + "catch": function(callback) { + return this.then(null, callback); + }, + + "finally": function(callback) { + + function makePromise(value, resolved) { + var result = defer(); + if (resolved) { + result.resolve(value); + } else { + result.reject(value); + } + return result.promise; + } + + function handleCallback(value, isResolved) { + var callbackOutput = null; + try { + callbackOutput = (callback ||defaultCallback)(); + } catch(e) { + return makePromise(e, false); + } + if (callbackOutput && isFunction(callbackOutput.then)) { + return callbackOutput.then(function() { + return makePromise(value, isResolved); + }, function(error) { + return makePromise(error, false); + }); + } else { + return makePromise(value, isResolved); + } + } + + return this.then(function(value) { + return handleCallback(value, true); + }, function(error) { + return handleCallback(error, false); + }); + } + } + }; + + return deferred; + }; + + + var ref = function(value) { + if (value && isFunction(value.then)) return value; + return { + then: function(callback) { + var result = defer(); + nextTick(function() { + result.resolve(callback(value)); + }); + return result.promise; + } + }; + }; + + + /** + * @ngdoc + * @name ng.$q#reject + * @methodOf ng.$q + * @description + * Creates a promise that is resolved as rejected with the specified `reason`. This api should be + * used to forward rejection in a chain of promises. If you are dealing with the last promise in + * a promise chain, you don't need to worry about it. + * + * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of + * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via + * a promise error callback and you want to forward the error to the promise derived from the + * current promise, you have to "rethrow" the error by returning a rejection constructed via + * `reject`. + * + *
      +   *   promiseB = promiseA.then(function(result) {
      +   *     // success: do something and resolve promiseB
      +   *     //          with the old or a new result
      +   *     return result;
      +   *   }, function(reason) {
      +   *     // error: handle the error if possible and
      +   *     //        resolve promiseB with newPromiseOrValue,
      +   *     //        otherwise forward the rejection to promiseB
      +   *     if (canHandle(reason)) {
      +   *      // handle the error and recover
      +   *      return newPromiseOrValue;
      +   *     }
      +   *     return $q.reject(reason);
      +   *   });
      +   * 
      + * + * @param {*} reason Constant, message, exception or an object representing the rejection reason. + * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. + */ + var reject = function(reason) { + return { + then: function(callback, errback) { + var result = defer(); + nextTick(function() { + try { + result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }); + return result.promise; + } + }; + }; + + + /** + * @ngdoc + * @name ng.$q#when + * @methodOf ng.$q + * @description + * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. + * This is useful when you are dealing with an object that might or might not be a promise, or if + * the promise comes from a source that can't be trusted. + * + * @param {*} value Value or a promise + * @returns {Promise} Returns a promise of the passed value or promise + */ + var when = function(value, callback, errback, progressback) { + var result = defer(), + done; + + var wrappedCallback = function(value) { + try { + return (isFunction(callback) ? callback : defaultCallback)(value); + } catch (e) { + exceptionHandler(e); + return reject(e); + } + }; + + var wrappedErrback = function(reason) { + try { + return (isFunction(errback) ? errback : defaultErrback)(reason); + } catch (e) { + exceptionHandler(e); + return reject(e); + } + }; + + var wrappedProgressback = function(progress) { + try { + return (isFunction(progressback) ? progressback : defaultCallback)(progress); + } catch (e) { + exceptionHandler(e); + } + }; + + nextTick(function() { + ref(value).then(function(value) { + if (done) return; + done = true; + result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); + }, function(reason) { + if (done) return; + done = true; + result.resolve(wrappedErrback(reason)); + }, function(progress) { + if (done) return; + result.notify(wrappedProgressback(progress)); + }); + }); + + return result.promise; + }; + + + function defaultCallback(value) { + return value; + } + + + function defaultErrback(reason) { + return reject(reason); + } + + + /** + * @ngdoc + * @name ng.$q#all + * @methodOf ng.$q + * @description + * Combines multiple promises into a single promise that is resolved when all of the input + * promises are resolved. + * + * @param {Array.|Object.} promises An array or hash of promises. + * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, + * each value corresponding to the promise at the same index/key in the `promises` array/hash. + * If any of the promises is resolved with a rejection, this resulting promise will be rejected + * with the same rejection value. + */ + function all(promises) { + var deferred = defer(), + counter = 0, + results = isArray(promises) ? [] : {}; + + forEach(promises, function(promise, key) { + counter++; + ref(promise).then(function(value) { + if (results.hasOwnProperty(key)) return; + results[key] = value; + if (!(--counter)) deferred.resolve(results); + }, function(reason) { + if (results.hasOwnProperty(key)) return; + deferred.reject(reason); + }); + }); + + if (counter === 0) { + deferred.resolve(results); + } + + return deferred.promise; + } + + return { + defer: defer, + reject: reject, + when: when, + all: all + }; +} + +/** + * DESIGN NOTES + * + * The design decisions behind the scope are heavily favored for speed and memory consumption. + * + * The typical use of scope is to watch the expressions, which most of the time return the same + * value as last time so we optimize the operation. + * + * Closures construction is expensive in terms of speed as well as memory: + * - No closures, instead use prototypical inheritance for API + * - Internal state needs to be stored on scope directly, which means that private state is + * exposed as $$____ properties + * + * Loop operations are optimized by using while(count--) { ... } + * - this means that in order to keep the same order of execution as addition we have to add + * items to the array at the beginning (shift) instead of at the end (push) + * + * Child scopes are created and removed often + * - Using an array would be slow since inserts in middle are expensive so we use linked list + * + * There are few watches then a lot of observers. This is why you don't want the observer to be + * implemented in the same way as watch. Watch requires return of initialization function which + * are expensive to construct. + */ + + +/** + * @ngdoc object + * @name ng.$rootScopeProvider + * @description + * + * Provider for the $rootScope service. + */ + +/** + * @ngdoc function + * @name ng.$rootScopeProvider#digestTtl + * @methodOf ng.$rootScopeProvider + * @description + * + * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and + * assuming that the model is unstable. + * + * The current default is 10 iterations. + * + * In complex applications it's possible that the dependencies between `$watch`s will result in + * several digest iterations. However if an application needs more than the default 10 digest + * iterations for its model to stabilize then you should investigate what is causing the model to + * continuously change during the digest. + * + * Increasing the TTL could have performance implications, so you should not change it without + * proper justification. + * + * @param {number} limit The number of digest iterations. + */ + + +/** + * @ngdoc object + * @name ng.$rootScope + * @description + * + * Every application has a single root {@link ng.$rootScope.Scope scope}. + * All other scopes are descendant scopes of the root scope. Scopes provide separation + * between the model and the view, via a mechanism for watching the model for changes. + * They also provide an event emission/broadcast and subscription facility. See the + * {@link guide/scope developer guide on scopes}. + */ +function $RootScopeProvider(){ + var TTL = 10; + var $rootScopeMinErr = minErr('$rootScope'); + + this.digestTtl = function(value) { + if (arguments.length) { + TTL = value; + } + return TTL; + }; + + this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', + function( $injector, $exceptionHandler, $parse, $browser) { + + /** + * @ngdoc function + * @name ng.$rootScope.Scope + * + * @description + * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the + * {@link AUTO.$injector $injector}. Child scopes are created using the + * {@link ng.$rootScope.Scope#methods_$new $new()} method. (Most scopes are created automatically when + * compiled HTML template is executed.) + * + * Here is a simple scope snippet to show how you can interact with the scope. + *
      +     * 
      +     * 
      + * + * # Inheritance + * A scope can inherit from a parent scope, as in this example: + *
      +         var parent = $rootScope;
      +         var child = parent.$new();
      +
      +         parent.salutation = "Hello";
      +         child.name = "World";
      +         expect(child.salutation).toEqual('Hello');
      +
      +         child.salutation = "Welcome";
      +         expect(child.salutation).toEqual('Welcome');
      +         expect(parent.salutation).toEqual('Hello');
      +     * 
      + * + * + * @param {Object.=} providers Map of service factory which need to be + * provided for the current scope. Defaults to {@link ng}. + * @param {Object.=} instanceCache Provides pre-instantiated services which should + * append/override services provided by `providers`. This is handy + * when unit-testing and having the need to override a default + * service. + * @returns {Object} Newly created scope. + * + */ + function Scope() { + this.$id = nextUid(); + this.$$phase = this.$parent = this.$$watchers = + this.$$nextSibling = this.$$prevSibling = + this.$$childHead = this.$$childTail = null; + this['this'] = this.$root = this; + this.$$destroyed = false; + this.$$asyncQueue = []; + this.$$postDigestQueue = []; + this.$$listeners = {}; + this.$$isolateBindings = {}; + } + + /** + * @ngdoc property + * @name ng.$rootScope.Scope#$id + * @propertyOf ng.$rootScope.Scope + * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for + * debugging. + */ + + + Scope.prototype = { + constructor: Scope, + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$new + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Creates a new child {@link ng.$rootScope.Scope scope}. + * + * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and + * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the + * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. + * + * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is + * desired for the scope and its child scopes to be permanently detached from the parent and + * thus stop participating in model change detection and listener notification by invoking. + * + * @param {boolean} isolate If true, then the scope does not prototypically inherit from the + * parent scope. The scope is isolated, as it can not see parent scope properties. + * When creating widgets, it is useful for the widget to not accidentally read parent + * state. + * + * @returns {Object} The newly created child scope. + * + */ + $new: function(isolate) { + var Child, + child; + + if (isolate) { + child = new Scope(); + child.$root = this.$root; + // ensure that there is just one async queue per $rootScope and its children + child.$$asyncQueue = this.$$asyncQueue; + child.$$postDigestQueue = this.$$postDigestQueue; + } else { + Child = function() {}; // should be anonymous; This is so that when the minifier munges + // the name it does not become random set of chars. This will then show up as class + // name in the debugger. + Child.prototype = this; + child = new Child(); + child.$id = nextUid(); + } + child['this'] = child; + child.$$listeners = {}; + child.$parent = this; + child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; + child.$$prevSibling = this.$$childTail; + if (this.$$childHead) { + this.$$childTail.$$nextSibling = child; + this.$$childTail = child; + } else { + this.$$childHead = this.$$childTail = child; + } + return child; + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$watch + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Registers a `listener` callback to be executed whenever the `watchExpression` changes. + * + * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest + * $digest()} and should return the value that will be watched. (Since + * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the + * `watchExpression` can execute multiple times per + * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) + * - The `listener` is called only when the value from the current `watchExpression` and the + * previous call to `watchExpression` are not equal (with the exception of the initial run, + * see below). The inequality is determined according to + * {@link angular.equals} function. To save the value of the object for later comparison, + * the {@link angular.copy} function is used. It also means that watching complex options + * will have adverse memory and performance implications. + * - The watch `listener` may change the model, which may trigger other `listener`s to fire. + * This is achieved by rerunning the watchers until no changes are detected. The rerun + * iteration limit is 10 to prevent an infinite loop deadlock. + * + * + * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, + * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` + * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a + * change is detected, be prepared for multiple calls to your listener.) + * + * After a watcher is registered with the scope, the `listener` fn is called asynchronously + * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the + * watcher. In rare cases, this is undesirable because the listener is called when the result + * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you + * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the + * listener was called due to initialization. + * + * The example below contains an illustration of using a function as your $watch listener + * + * + * # Example + *
      +           // let's assume that scope was dependency injected as the $rootScope
      +           var scope = $rootScope;
      +           scope.name = 'misko';
      +           scope.counter = 0;
      +
      +           expect(scope.counter).toEqual(0);
      +           scope.$watch('name', function(newValue, oldValue) {
      +             scope.counter = scope.counter + 1;
      +           });
      +           expect(scope.counter).toEqual(0);
      +
      +           scope.$digest();
      +           // no variable change
      +           expect(scope.counter).toEqual(0);
      +
      +           scope.name = 'adam';
      +           scope.$digest();
      +           expect(scope.counter).toEqual(1);
      +
      +
      +
      +           // Using a listener function 
      +           var food;
      +           scope.foodCounter = 0;
      +           expect(scope.foodCounter).toEqual(0);
      +           scope.$watch(
      +             // This is the listener function
      +             function() { return food; },
      +             // This is the change handler
      +             function(newValue, oldValue) {
      +               if ( newValue !== oldValue ) {
      +                 // Only increment the counter if the value changed
      +                 scope.foodCounter = scope.foodCounter + 1;
      +               }
      +             }
      +           );
      +           // No digest has been run so the counter will be zero
      +           expect(scope.foodCounter).toEqual(0);
      +
      +           // Run the digest but since food has not changed cout will still be zero
      +           scope.$digest();
      +           expect(scope.foodCounter).toEqual(0);
      +
      +           // Update food and run digest.  Now the counter will increment
      +           food = 'cheeseburger';
      +           scope.$digest();
      +           expect(scope.foodCounter).toEqual(1);  
      +
      +       * 
      + * + * + * + * @param {(function()|string)} watchExpression Expression that is evaluated on each + * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers + * a call to the `listener`. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(scope)`: called with current `scope` as a parameter. + * @param {(function()|string)=} listener Callback called whenever the return value of + * the `watchExpression` changes. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(newValue, oldValue, scope)`: called with current and previous values as + * parameters. + * + * @param {boolean=} objectEquality Compare object for equality rather than for reference. + * @returns {function()} Returns a deregistration function for this listener. + */ + $watch: function(watchExp, listener, objectEquality) { + var scope = this, + get = compileToFn(watchExp, 'watch'), + array = scope.$$watchers, + watcher = { + fn: listener, + last: initWatchVal, + get: get, + exp: watchExp, + eq: !!objectEquality + }; + + // in the case user pass string, we need to compile it, do we really need this ? + if (!isFunction(listener)) { + var listenFn = compileToFn(listener || noop, 'listener'); + watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; + } + + if (typeof watchExp == 'string' && get.constant) { + var originalFn = watcher.fn; + watcher.fn = function(newVal, oldVal, scope) { + originalFn.call(this, newVal, oldVal, scope); + arrayRemove(array, watcher); + }; + } + + if (!array) { + array = scope.$$watchers = []; + } + // we use unshift since we use a while loop in $digest for speed. + // the while loop reads in reverse order. + array.unshift(watcher); + + return function() { + arrayRemove(array, watcher); + }; + }, + + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$watchCollection + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Shallow watches the properties of an object and fires whenever any of the properties change + * (for arrays, this implies watching the array items; for object maps, this implies watching + * the properties). If a change is detected, the `listener` callback is fired. + * + * - The `obj` collection is observed via standard $watch operation and is examined on every + * call to $digest() to see if any items have been added, removed, or moved. + * - The `listener` is called whenever anything within the `obj` has changed. Examples include + * adding, removing, and moving items belonging to an object or array. + * + * + * # Example + *
      +          $scope.names = ['igor', 'matias', 'misko', 'james'];
      +          $scope.dataCount = 4;
      +
      +          $scope.$watchCollection('names', function(newNames, oldNames) {
      +            $scope.dataCount = newNames.length;
      +          });
      +
      +          expect($scope.dataCount).toEqual(4);
      +          $scope.$digest();
      +
      +          //still at 4 ... no changes
      +          expect($scope.dataCount).toEqual(4);
      +
      +          $scope.names.pop();
      +          $scope.$digest();
      +
      +          //now there's been a change
      +          expect($scope.dataCount).toEqual(3);
      +       * 
      + * + * + * @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The + * expression value should evaluate to an object or an array which is observed on each + * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the + * collection will trigger a call to the `listener`. + * + * @param {function(newCollection, oldCollection, scope)} listener a callback function that is + * fired with both the `newCollection` and `oldCollection` as parameters. + * The `newCollection` object is the newly modified data obtained from the `obj` expression + * and the `oldCollection` object is a copy of the former collection data. + * The `scope` refers to the current scope. + * + * @returns {function()} Returns a de-registration function for this listener. When the + * de-registration function is executed, the internal watch operation is terminated. + */ + $watchCollection: function(obj, listener) { + var self = this; + var oldValue; + var newValue; + var changeDetected = 0; + var objGetter = $parse(obj); + var internalArray = []; + var internalObject = {}; + var oldLength = 0; + + function $watchCollectionWatch() { + newValue = objGetter(self); + var newLength, key; + + if (!isObject(newValue)) { + if (oldValue !== newValue) { + oldValue = newValue; + changeDetected++; + } + } else if (isArrayLike(newValue)) { + if (oldValue !== internalArray) { + // we are transitioning from something which was not an array into array. + oldValue = internalArray; + oldLength = oldValue.length = 0; + changeDetected++; + } + + newLength = newValue.length; + + if (oldLength !== newLength) { + // if lengths do not match we need to trigger change notification + changeDetected++; + oldValue.length = oldLength = newLength; + } + // copy the items to oldValue and look for changes. + for (var i = 0; i < newLength; i++) { + if (oldValue[i] !== newValue[i]) { + changeDetected++; + oldValue[i] = newValue[i]; + } + } + } else { + if (oldValue !== internalObject) { + // we are transitioning from something which was not an object into object. + oldValue = internalObject = {}; + oldLength = 0; + changeDetected++; + } + // copy the items to oldValue and look for changes. + newLength = 0; + for (key in newValue) { + if (newValue.hasOwnProperty(key)) { + newLength++; + if (oldValue.hasOwnProperty(key)) { + if (oldValue[key] !== newValue[key]) { + changeDetected++; + oldValue[key] = newValue[key]; + } + } else { + oldLength++; + oldValue[key] = newValue[key]; + changeDetected++; + } + } + } + if (oldLength > newLength) { + // we used to have more keys, need to find them and destroy them. + changeDetected++; + for(key in oldValue) { + if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { + oldLength--; + delete oldValue[key]; + } + } + } + } + return changeDetected; + } + + function $watchCollectionAction() { + listener(newValue, oldValue, self); + } + + return this.$watch($watchCollectionWatch, $watchCollectionAction); + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$digest + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and + * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change + * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} + * until no more listeners are firing. This means that it is possible to get into an infinite + * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of + * iterations exceeds 10. + * + * Usually, you don't call `$digest()` directly in + * {@link ng.directive:ngController controllers} or in + * {@link ng.$compileProvider#methods_directive directives}. + * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within + * a {@link ng.$compileProvider#methods_directive directives}), which will force a `$digest()`. + * + * If you want to be notified whenever `$digest()` is called, + * you can register a `watchExpression` function with + * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. + * + * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. + * + * # Example + *
      +           var scope = ...;
      +           scope.name = 'misko';
      +           scope.counter = 0;
      +
      +           expect(scope.counter).toEqual(0);
      +           scope.$watch('name', function(newValue, oldValue) {
      +             scope.counter = scope.counter + 1;
      +           });
      +           expect(scope.counter).toEqual(0);
      +
      +           scope.$digest();
      +           // no variable change
      +           expect(scope.counter).toEqual(0);
      +
      +           scope.name = 'adam';
      +           scope.$digest();
      +           expect(scope.counter).toEqual(1);
      +       * 
      + * + */ + $digest: function() { + var watch, value, last, + watchers, + asyncQueue = this.$$asyncQueue, + postDigestQueue = this.$$postDigestQueue, + length, + dirty, ttl = TTL, + next, current, target = this, + watchLog = [], + logIdx, logMsg, asyncTask; + + beginPhase('$digest'); + + do { // "while dirty" loop + dirty = false; + current = target; + + while(asyncQueue.length) { + try { + asyncTask = asyncQueue.shift(); + asyncTask.scope.$eval(asyncTask.expression); + } catch (e) { + $exceptionHandler(e); + } + } + + do { // "traverse the scopes" loop + if ((watchers = current.$$watchers)) { + // process our watches + length = watchers.length; + while (length--) { + try { + watch = watchers[length]; + // Most common watches are on primitives, in which case we can short + // circuit it with === operator, only when === fails do we use .equals + if (watch && (value = watch.get(current)) !== (last = watch.last) && + !(watch.eq + ? equals(value, last) + : (typeof value == 'number' && typeof last == 'number' + && isNaN(value) && isNaN(last)))) { + dirty = true; + watch.last = watch.eq ? copy(value) : value; + watch.fn(value, ((last === initWatchVal) ? value : last), current); + if (ttl < 5) { + logIdx = 4 - ttl; + if (!watchLog[logIdx]) watchLog[logIdx] = []; + logMsg = (isFunction(watch.exp)) + ? 'fn: ' + (watch.exp.name || watch.exp.toString()) + : watch.exp; + logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); + watchLog[logIdx].push(logMsg); + } + } + } catch (e) { + $exceptionHandler(e); + } + } + } + + // Insanity Warning: scope depth-first traversal + // yes, this code is a bit crazy, but it works and we have tests to prove it! + // this piece should be kept in sync with the traversal in $broadcast + if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { + while(current !== target && !(next = current.$$nextSibling)) { + current = current.$parent; + } + } + } while ((current = next)); + + if(dirty && !(ttl--)) { + clearPhase(); + throw $rootScopeMinErr('infdig', + '{0} $digest() iterations reached. Aborting!\n' + + 'Watchers fired in the last 5 iterations: {1}', + TTL, toJson(watchLog)); + } + } while (dirty || asyncQueue.length); + + clearPhase(); + + while(postDigestQueue.length) { + try { + postDigestQueue.shift()(); + } catch (e) { + $exceptionHandler(e); + } + } + }, + + + /** + * @ngdoc event + * @name ng.$rootScope.Scope#$destroy + * @eventOf ng.$rootScope.Scope + * @eventType broadcast on scope being destroyed + * + * @description + * Broadcasted when a scope and its children are being destroyed. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$destroy + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Removes the current scope (and all of its children) from the parent scope. Removal implies + * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer + * propagate to the current scope and its children. Removal also implies that the current + * scope is eligible for garbage collection. + * + * The `$destroy()` is usually used by directives such as + * {@link ng.directive:ngRepeat ngRepeat} for managing the + * unrolling of the loop. + * + * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. + * Application code can register a `$destroy` event handler that will give it a chance to + * perform any necessary cleanup. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + $destroy: function() { + // we can't destroy the root scope or a scope that has been already destroyed + if ($rootScope == this || this.$$destroyed) return; + var parent = this.$parent; + + this.$broadcast('$destroy'); + this.$$destroyed = true; + + if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; + if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; + if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; + if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; + + // This is bogus code that works around Chrome's GC leak + // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 + this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = + this.$$childTail = null; + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$eval + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Executes the `expression` on the current scope and returns the result. Any exceptions in + * the expression are propagated (uncaught). This is useful when evaluating Angular + * expressions. + * + * # Example + *
      +           var scope = ng.$rootScope.Scope();
      +           scope.a = 1;
      +           scope.b = 2;
      +
      +           expect(scope.$eval('a+b')).toEqual(3);
      +           expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
      +       * 
      + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + * @param {(object)=} locals Local variables object, useful for overriding values in scope. + * @returns {*} The result of evaluating the expression. + */ + $eval: function(expr, locals) { + return $parse(expr)(this, locals); + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$evalAsync + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Executes the expression on the current scope at a later point in time. + * + * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only + * that: + * + * - it will execute after the function that scheduled the evaluation (preferably before DOM + * rendering). + * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after + * `expression` execution. + * + * Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle + * will be scheduled. However, it is encouraged to always call code that changes the model + * from within an `$apply` call. That includes code evaluated via `$evalAsync`. + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + */ + $evalAsync: function(expr) { + // if we are outside of an $digest loop and this is the first time we are scheduling async + // task also schedule async auto-flush + if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { + $browser.defer(function() { + if ($rootScope.$$asyncQueue.length) { + $rootScope.$digest(); + } + }); + } + + this.$$asyncQueue.push({scope: this, expression: expr}); + }, + + $$postDigest : function(fn) { + this.$$postDigestQueue.push(fn); + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$apply + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * `$apply()` is used to execute an expression in angular from outside of the angular + * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). + * Because we are calling into the angular framework we need to perform proper scope life + * cycle of {@link ng.$exceptionHandler exception handling}, + * {@link ng.$rootScope.Scope#$digest executing watches}. + * + * ## Life cycle + * + * # Pseudo-Code of `$apply()` + *
      +           function $apply(expr) {
      +             try {
      +               return $eval(expr);
      +             } catch (e) {
      +               $exceptionHandler(e);
      +             } finally {
      +               $root.$digest();
      +             }
      +           }
      +       * 
      + * + * + * Scope's `$apply()` method transitions through the following stages: + * + * 1. The {@link guide/expression expression} is executed using the + * {@link ng.$rootScope.Scope#$eval $eval()} method. + * 2. Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the + * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. + * + * + * @param {(string|function())=} exp An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with current `scope` parameter. + * + * @returns {*} The result of evaluating the expression. + */ + $apply: function(expr) { + try { + beginPhase('$apply'); + return this.$eval(expr); + } catch (e) { + $exceptionHandler(e); + } finally { + clearPhase(); + try { + $rootScope.$digest(); + } catch (e) { + $exceptionHandler(e); + throw e; + } + } + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$on + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for + * discussion of event life cycle. + * + * The event listener function format is: `function(event, args...)`. The `event` object + * passed into the listener has the following attributes: + * + * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or + * `$broadcast`-ed. + * - `currentScope` - `{Scope}`: the current scope which is handling the event. + * - `name` - `{string}`: name of the event. + * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel + * further event propagation (available only for events that were `$emit`-ed). + * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag + * to true. + * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. + * + * @param {string} name Event name to listen on. + * @param {function(event, args...)} listener Function to call when the event is emitted. + * @returns {function()} Returns a deregistration function for this listener. + */ + $on: function(name, listener) { + var namedListeners = this.$$listeners[name]; + if (!namedListeners) { + this.$$listeners[name] = namedListeners = []; + } + namedListeners.push(listener); + + return function() { + namedListeners[indexOf(namedListeners, listener)] = null; + }; + }, + + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$emit + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Dispatches an event `name` upwards through the scope hierarchy notifying the + * registered {@link ng.$rootScope.Scope#$on} listeners. + * + * The event life cycle starts at the scope on which `$emit` was called. All + * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get + * notified. Afterwards, the event traverses upwards toward the root scope and calls all + * registered listeners along the way. The event will stop propagating if one of the listeners + * cancels it. + * + * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed + * onto the {@link ng.$exceptionHandler $exceptionHandler} service. + * + * @param {string} name Event name to emit. + * @param {...*} args Optional set of arguments which will be passed onto the event listeners. + * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). + */ + $emit: function(name, args) { + var empty = [], + namedListeners, + scope = this, + stopPropagation = false, + event = { + name: name, + targetScope: scope, + stopPropagation: function() {stopPropagation = true;}, + preventDefault: function() { + event.defaultPrevented = true; + }, + defaultPrevented: false + }, + listenerArgs = concat([event], arguments, 1), + i, length; + + do { + namedListeners = scope.$$listeners[name] || empty; + event.currentScope = scope; + for (i=0, length=namedListeners.length; i -1) { + throw $sceMinErr('iwcard', + 'Illegal sequence *** in string matcher. String: {0}', matcher); + } + matcher = escapeForRegexp(matcher). + replace('\\*\\*', '.*'). + replace('\\*', '[^:/.?&;]*'); + return new RegExp('^' + matcher + '$'); + } else if (isRegExp(matcher)) { + // The only other type of matcher allowed is a Regexp. + // Match entire URL / disallow partial matches. + // Flags are reset (i.e. no global, ignoreCase or multiline) + return new RegExp('^' + matcher.source + '$'); + } else { + throw $sceMinErr('imatcher', + 'Matchers may only be "self", string patterns or RegExp objects'); + } +} + + +function adjustMatchers(matchers) { + var adjustedMatchers = []; + if (isDefined(matchers)) { + forEach(matchers, function(matcher) { + adjustedMatchers.push(adjustMatcher(matcher)); + }); + } + return adjustedMatchers; +} + + +/** + * @ngdoc service + * @name ng.$sceDelegate + * @function + * + * @description + * + * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict + * Contextual Escaping (SCE)} services to AngularJS. + * + * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of + * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is + * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to + * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things + * work because `$sce` delegates to `$sceDelegate` for these operations. + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. + * + * The default instance of `$sceDelegate` should work out of the box with little pain. While you + * can override it completely to change the behavior of `$sce`, the common case would + * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting + * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as + * templates. Refer {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist + * $sceDelegateProvider.resourceUrlWhitelist} and {@link + * ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + */ + +/** + * @ngdoc object + * @name ng.$sceDelegateProvider + * @description + * + * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate + * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure + * that the URLs used for sourcing Angular templates are safe. Refer {@link + * ng.$sceDelegateProvider#methods_resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and + * {@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + * + * For the general details about this service in Angular, read the main page for {@link ng.$sce + * Strict Contextual Escaping (SCE)}. + * + * **Example**: Consider the following case. + * + * - your app is hosted at url `http://myapp.example.com/` + * - but some of your templates are hosted on other domains you control such as + * `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc. + * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. + * + * Here is what a secure configuration for this scenario might look like: + * + *
      + *    angular.module('myApp', []).config(function($sceDelegateProvider) {
      + *      $sceDelegateProvider.resourceUrlWhitelist([
      + *        // Allow same origin resource loads.
      + *        'self',
      + *        // Allow loading from our assets domain.  Notice the difference between * and **.
      + *        'http://srv*.assets.example.com/**']);
      + *
      + *      // The blacklist overrides the whitelist so the open redirect here is blocked.
      + *      $sceDelegateProvider.resourceUrlBlacklist([
      + *        'http://myapp.example.com/clickThru**']);
      + *      });
      + * 
      + */ + +function $SceDelegateProvider() { + this.SCE_CONTEXTS = SCE_CONTEXTS; + + // Resource URLs can also be trusted by policy. + var resourceUrlWhitelist = ['self'], + resourceUrlBlacklist = []; + + /** + * @ngdoc function + * @name ng.sceDelegateProvider#resourceUrlWhitelist + * @methodOf ng.$sceDelegateProvider + * @function + * + * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. + * + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. + * + * Note: **an empty whitelist array will block all URLs**! + * + * @return {Array} the currently set whitelist array. + * + * The **default value** when no whitelist has been explicitly set is `['self']` allowing only + * same origin resource requests. + * + * @description + * Sets/Gets the whitelist of trusted resource URLs. + */ + this.resourceUrlWhitelist = function (value) { + if (arguments.length) { + resourceUrlWhitelist = adjustMatchers(value); + } + return resourceUrlWhitelist; + }; + + /** + * @ngdoc function + * @name ng.sceDelegateProvider#resourceUrlBlacklist + * @methodOf ng.$sceDelegateProvider + * @function + * + * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value + * provided. This must be an array or null. A snapshot of this array is used so further + * changes to the array are ignored. + * + * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items + * allowed in this array. + * + * The typical usage for the blacklist is to **block + * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as + * these would otherwise be trusted but actually return content from the redirected domain. + * + * Finally, **the blacklist overrides the whitelist** and has the final say. + * + * @return {Array} the currently set blacklist array. + * + * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there + * is no blacklist.) + * + * @description + * Sets/Gets the blacklist of trusted resource URLs. + */ + + this.resourceUrlBlacklist = function (value) { + if (arguments.length) { + resourceUrlBlacklist = adjustMatchers(value); + } + return resourceUrlBlacklist; + }; + + this.$get = ['$log', '$document', '$injector', function( + $log, $document, $injector) { + + var htmlSanitizer = function htmlSanitizer(html) { + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + }; + + if ($injector.has('$sanitize')) { + htmlSanitizer = $injector.get('$sanitize'); + } + + + function matchUrl(matcher, parsedUrl) { + if (matcher === 'self') { + return urlIsSameOrigin(parsedUrl); + } else { + // definitely a regex. See adjustMatchers() + return !!matcher.exec(parsedUrl.href); + } + } + + function isResourceUrlAllowedByPolicy(url) { + var parsedUrl = urlResolve(url.toString()); + var i, n, allowed = false; + // Ensure that at least one item from the whitelist allows this url. + for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { + if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { + allowed = true; + break; + } + } + if (allowed) { + // Ensure that no item from the blacklist blocked this url. + for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { + if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { + allowed = false; + break; + } + } + } + return allowed; + } + + function generateHolderType(Base) { + var holderType = function TrustedValueHolderType(trustedValue) { + this.$$unwrapTrustedValue = function() { + return trustedValue; + }; + }; + if (Base) { + holderType.prototype = new Base(); + } + holderType.prototype.valueOf = function sceValueOf() { + return this.$$unwrapTrustedValue(); + }; + holderType.prototype.toString = function sceToString() { + return this.$$unwrapTrustedValue().toString(); + }; + return holderType; + } + + var trustedValueHolderBase = generateHolderType(), + byType = {}; + + byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); + + /** + * @ngdoc method + * @name ng.$sceDelegate#trustAs + * @methodOf ng.$sceDelegate + * + * @description + * Returns an object that is trusted by angular for use in specified strict + * contextual escaping contexts (such as ng-html-bind-unsafe, ng-include, any src + * attribute interpolation, any dom event binding attribute interpolation + * such as for onclick, etc.) that uses the provided value. + * See {@link ng.$sce $sce} for enabling strict contextual escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resourceUrl, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + function trustAs(type, trustedValue) { + var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (!Constructor) { + throw $sceMinErr('icontext', + 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', + type, trustedValue); + } + if (trustedValue === null || trustedValue === undefined || trustedValue === '') { + return trustedValue; + } + // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting + // mutable objects, we ensure here that the value passed in is actually a string. + if (typeof trustedValue !== 'string') { + throw $sceMinErr('itype', + 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', + type); + } + return new Constructor(trustedValue); + } + + /** + * @ngdoc method + * @name ng.$sceDelegate#valueOf + * @methodOf ng.$sceDelegate + * + * @description + * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link + * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. + * + * If the passed parameter is not a value that had been returned by {@link + * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}, returns it as-is. + * + * @param {*} value The result of a prior {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} + * call or anything else. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns + * `value` unchanged. + */ + function valueOf(maybeTrusted) { + if (maybeTrusted instanceof trustedValueHolderBase) { + return maybeTrusted.$$unwrapTrustedValue(); + } else { + return maybeTrusted; + } + } + + /** + * @ngdoc method + * @name ng.$sceDelegate#getTrusted + * @methodOf ng.$sceDelegate + * + * @description + * Takes the result of a {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} call and + * returns the originally supplied value if the queried context type is a supertype of the + * created type. If this condition isn't satisfied, throws an exception. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`} call. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs + * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. + */ + function getTrusted(type, maybeTrusted) { + if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { + return maybeTrusted; + } + var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (constructor && maybeTrusted instanceof constructor) { + return maybeTrusted.$$unwrapTrustedValue(); + } + // If we get here, then we may only take one of two actions. + // 1. sanitize the value for the requested type, or + // 2. throw an exception. + if (type === SCE_CONTEXTS.RESOURCE_URL) { + if (isResourceUrlAllowedByPolicy(maybeTrusted)) { + return maybeTrusted; + } else { + throw $sceMinErr('insecurl', + 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', + maybeTrusted.toString()); + } + } else if (type === SCE_CONTEXTS.HTML) { + return htmlSanitizer(maybeTrusted); + } + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + } + + return { trustAs: trustAs, + getTrusted: getTrusted, + valueOf: valueOf }; + }]; +} + + +/** + * @ngdoc object + * @name ng.$sceProvider + * @description + * + * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. + * - enable/disable Strict Contextual Escaping (SCE) in a module + * - override the default implementation with a custom delegate + * + * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. + */ + +/* jshint maxlen: false*/ + +/** + * @ngdoc service + * @name ng.$sce + * @function + * + * @description + * + * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. + * + * # Strict Contextual Escaping + * + * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain + * contexts to result in a value that is marked as safe to use for that context. One example of + * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer + * to these contexts as privileged or SCE contexts. + * + * As of version 1.2, Angular ships with SCE enabled by default. + * + * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows + * one to execute arbitrary javascript by the use of the expression() syntax. Refer + * to learn more about them. + * You can ensure your document is in standards mode and not quirks mode by adding `` + * to the top of your HTML document. + * + * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for + * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. + * + * Here's an example of a binding in a privileged context: + * + *
      + *     
      + *     
      + *
      + * + * Notice that `ng-bind-html` is bound to `{{userHtml}}` controlled by the user. With SCE + * disabled, this application allows the user to render arbitrary HTML into the DIV. + * In a more realistic example, one may be rendering user comments, blog articles, etc. via + * bindings. (HTML is just one example of a context where rendering user controlled input creates + * security vulnerabilities.) + * + * For the case of HTML, you might use a library, either on the client side, or on the server side, + * to sanitize unsafe HTML before binding to the value and rendering it in the document. + * + * How would you ensure that every place that used these types of bindings was bound to a value that + * was sanitized by your library (or returned as safe for rendering by your server?) How can you + * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some + * properties/fields and forgot to update the binding to the sanitized value? + * + * To be secure by default, you want to ensure that any such bindings are disallowed unless you can + * determine that something explicitly says it's safe to use a value for binding in that + * context. You can then audit your code (a simple grep would do) to ensure that this is only done + * for those values that you can easily tell are safe - because they were received from your server, + * sanitized by your library, etc. You can organize your codebase to help with this - perhaps + * allowing only the files in a specific directory to do this. Ensuring that the internal API + * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. + * + * In the case of AngularJS' SCE service, one uses {@link ng.$sce#methods_trustAs $sce.trustAs} + * (and shorthand methods such as {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}, etc.) to + * obtain values that will be accepted by SCE / privileged contexts. + * + * + * ## How does it work? + * + * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#methods_getTrusted + * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link + * ng.$sce#methods_parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the + * {@link ng.$sce#methods_getTrusted $sce.getTrusted} behind the scenes on non-constant literals. + * + * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link + * ng.$sce#methods_parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly + * simplified): + * + *
      + *   var ngBindHtmlDirective = ['$sce', function($sce) {
      + *     return function(scope, element, attr) {
      + *       scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
      + *         element.html(value || '');
      + *       });
      + *     };
      + *   }];
      + * 
      + * + * ## Impact on loading templates + * + * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as + * `templateUrl`'s specified by {@link guide/directive directives}. + * + * By default, Angular only loads templates from the same domain and protocol as the application + * document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or + * protocols, you may either either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist + * them} or {@link ng.$sce#methods_trustAsResourceUrl wrap it} into a trusted value. + * + * *Please note*: + * The browser's + * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest + * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing (CORS)} + * policy apply in addition to this and may further restrict whether the template is successfully + * loaded. This means that without the right CORS policy, loading templates from a different domain + * won't work on all browsers. Also, loading templates from `file://` URL does not work on some + * browsers. + * + * ## This feels like too much overhead for the developer? + * + * It's important to remember that SCE only applies to interpolation expressions. + * + * If your expressions are constant literals, they're automatically trusted and you don't need to + * call `$sce.trustAs` on them. (e.g. + * `
      `) just works. + * + * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them + * through {@link ng.$sce#methods_getTrusted $sce.getTrusted}. SCE doesn't play a role here. + * + * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load + * templates in `ng-include` from your application's domain without having to even know about SCE. + * It blocks loading templates from other domains or loading templates over http from an https + * served document. You can change these by setting your own custom {@link + * ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelists} and {@link + * ng.$sceDelegateProvider#methods_resourceUrlBlacklist blacklists} for matching such URLs. + * + * This significantly reduces the overhead. It is far easier to pay the small overhead and have an + * application that's secure and can be audited to verify that with much more ease than bolting + * security onto an application later. + * + * + * ## What trusted context types are supported? + * + * | Context | Notes | + * |---------------------|----------------| + * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | + * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | + * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
      Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | + * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | + * + * ## Format of items in {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist Blacklist}
      + * + * Each element in these arrays must be one of the following: + * + * - **'self'** + * - The special **string**, `'self'`, can be used to match against all URLs of the **same + * domain** as the application document using the **same protocol**. + * - **String** (except the special value `'self'`) + * - The string is matched against the full *normalized / absolute URL* of the resource + * being tested (substring matches are not good enough.) + * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters + * match themselves. + * - `*`: matches zero or more occurances of any character other than one of the following 6 + * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use + * in a whitelist. + * - `**`: matches zero or more occurances of *any* character. As such, it's not + * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. + * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might + * not have been the intention.) It's usage at the very end of the path is ok. (e.g. + * http://foo.example.com/templates/**). + * - **RegExp** (*see caveat below*) + * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax + * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to + * accidentally introduce a bug when one updates a complex expression (imho, all regexes should + * have good test coverage.). For instance, the use of `.` in the regex is correct only in a + * small number of cases. A `.` character in the regex used when matching the scheme or a + * subdomain could be matched against a `:` or literal `.` that was likely not intended. It + * is highly recommended to use the string patterns and only fall back to regular expressions + * if they as a last resort. + * - The regular expression must be an instance of RegExp (i.e. not a string.) It is + * matched against the **entire** *normalized / absolute URL* of the resource being tested + * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags + * present on the RegExp (such as multiline, global, ignoreCase) are ignored. + * - If you are generating your Javascript from some other templating engine (not + * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), + * remember to escape your regular expression (and be aware that you might need more than + * one level of escaping depending on your templating engine and the way you interpolated + * the value.) Do make use of your platform's escaping mechanism as it might be good + * enough before coding your own. e.g. Ruby has + * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) + * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). + * Javascript lacks a similar built in function for escaping. Take a look at Google + * Closure library's [goog.string.regExpEscape(s)]( + * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. + * + * ## Show me an example using SCE. + * + * @example + + +
      +

      + User comments
      + By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when + $sanitize is available. If $sanitize isn't available, this results in an error instead of an + exploit. +
      +
      + {{userComment.name}}: + +
      +
      +
      +
      +
      + + + var mySceApp = angular.module('mySceApp', ['ngSanitize']); + + mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { + var self = this; + $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { + self.userComments = userComments; + }); + self.explicitlyTrustedHtml = $sce.trustAsHtml( + 'Hover over this text.'); + }); + + + +[ + { "name": "Alice", + "htmlComment": + "Is anyone reading this?" + }, + { "name": "Bob", + "htmlComment": "Yes! Am I the only other one?" + } +] + + + + describe('SCE doc demo', function() { + it('should sanitize untrusted values', function() { + expect(element('.htmlComment').html()).toBe('Is anyone reading this?'); + }); + it('should NOT sanitize explicitly trusted values', function() { + expect(element('#explicitlyTrustedHtml').html()).toBe( + 'Hover over this text.'); + }); + }); + +
      + * + * + * + * ## Can I disable SCE completely? + * + * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits + * for little coding overhead. It will be much harder to take an SCE disabled application and + * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE + * for cases where you have a lot of existing code that was written before SCE was introduced and + * you're migrating them a module at a time. + * + * That said, here's how you can completely disable SCE: + * + *
      + *   angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
      + *     // Completely disable SCE.  For demonstration purposes only!
      + *     // Do not use in new projects.
      + *     $sceProvider.enabled(false);
      + *   });
      + * 
      + * + */ +/* jshint maxlen: 100 */ + +function $SceProvider() { + var enabled = true; + + /** + * @ngdoc function + * @name ng.sceProvider#enabled + * @methodOf ng.$sceProvider + * @function + * + * @param {boolean=} value If provided, then enables/disables SCE. + * @return {boolean} true if SCE is enabled, false otherwise. + * + * @description + * Enables/disables SCE and returns the current value. + */ + this.enabled = function (value) { + if (arguments.length) { + enabled = !!value; + } + return enabled; + }; + + + /* Design notes on the default implementation for SCE. + * + * The API contract for the SCE delegate + * ------------------------------------- + * The SCE delegate object must provide the following 3 methods: + * + * - trustAs(contextEnum, value) + * This method is used to tell the SCE service that the provided value is OK to use in the + * contexts specified by contextEnum. It must return an object that will be accepted by + * getTrusted() for a compatible contextEnum and return this value. + * + * - valueOf(value) + * For values that were not produced by trustAs(), return them as is. For values that were + * produced by trustAs(), return the corresponding input value to trustAs. Basically, if + * trustAs is wrapping the given values into some type, this operation unwraps it when given + * such a value. + * + * - getTrusted(contextEnum, value) + * This function should return the a value that is safe to use in the context specified by + * contextEnum or throw and exception otherwise. + * + * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be + * opaque or wrapped in some holder object. That happens to be an implementation detail. For + * instance, an implementation could maintain a registry of all trusted objects by context. In + * such a case, trustAs() would return the same object that was passed in. getTrusted() would + * return the same object passed in if it was found in the registry under a compatible context or + * throw an exception otherwise. An implementation might only wrap values some of the time based + * on some criteria. getTrusted() might return a value and not throw an exception for special + * constants or objects even if not wrapped. All such implementations fulfill this contract. + * + * + * A note on the inheritance model for SCE contexts + * ------------------------------------------------ + * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This + * is purely an implementation details. + * + * The contract is simply this: + * + * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) + * will also succeed. + * + * Inheritance happens to capture this in a natural way. In some future, we + * may not use inheritance anymore. That is OK because no code outside of + * sce.js and sceSpecs.js would need to be aware of this detail. + */ + + this.$get = ['$parse', '$document', '$sceDelegate', function( + $parse, $document, $sceDelegate) { + // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows + // the "expression(javascript expression)" syntax which is insecure. + if (enabled && msie) { + var documentMode = $document[0].documentMode; + if (documentMode !== undefined && documentMode < 8) { + throw $sceMinErr('iequirks', + 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + + 'mode. You can fix this by adding the text to the top of your HTML ' + + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); + } + } + + var sce = copy(SCE_CONTEXTS); + + /** + * @ngdoc function + * @name ng.sce#isEnabled + * @methodOf ng.$sce + * @function + * + * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you + * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. + * + * @description + * Returns a boolean indicating if SCE is enabled. + */ + sce.isEnabled = function () { + return enabled; + }; + sce.trustAs = $sceDelegate.trustAs; + sce.getTrusted = $sceDelegate.getTrusted; + sce.valueOf = $sceDelegate.valueOf; + + if (!enabled) { + sce.trustAs = sce.getTrusted = function(type, value) { return value; }; + sce.valueOf = identity; + } + + /** + * @ngdoc method + * @name ng.$sce#parse + * @methodOf ng.$sce + * + * @description + * Converts Angular {@link guide/expression expression} into a function. This is like {@link + * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it + * wraps the expression in a call to {@link ng.$sce#methods_getTrusted $sce.getTrusted(*type*, + * *result*)} + * + * @param {string} type The kind of SCE context in which this result will be used. + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + sce.parseAs = function sceParseAs(type, expr) { + var parsed = $parse(expr); + if (parsed.literal && parsed.constant) { + return parsed; + } else { + return function sceParseAsTrusted(self, locals) { + return sce.getTrusted(type, parsed(self, locals)); + }; + } + }; + + /** + * @ngdoc method + * @name ng.$sce#trustAs + * @methodOf ng.$sce + * + * @description + * Delegates to {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. As such, + * returns an objectthat is trusted by angular for use in specified strict contextual + * escaping contexts (such as ng-html-bind-unsafe, ng-include, any src attribute + * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) + * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual + * escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resource_url, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsHtml(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.HTML, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedHtml + * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsUrl(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedUrl + * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsResourceUrl(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedResourceUrl + * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the return + * value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsJs(value)` → + * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.JS, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedJs + * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrusted + * @methodOf ng.$sce + * + * @description + * Delegates to {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted`}. As such, + * takes the result of a {@link ng.$sce#methods_trustAs `$sce.trustAs`}() call and returns the + * originally supplied value if the queried context type is a supertype of the created type. + * If this condition isn't satisfied, throws an exception. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sce#methods_trustAs `$sce.trustAs`} + * call. + * @returns {*} The value the was originally provided to + * {@link ng.$sce#methods_trustAs `$sce.trustAs`} if valid in this context. + * Otherwise, throws an exception. + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedHtml(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedCss + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedCss(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedUrl(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedResourceUrl(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to pass to `$sceDelegate.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedJs(value)` → + * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsHtml(expression string)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.HTML, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsCss + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsCss(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.CSS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsUrl(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsResourceUrl(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.RESOURCE_URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsJs(value)` → + * {@link ng.$sce#methods_parse `$sce.parseAs($sce.JS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + // Shorthand delegations. + var parse = sce.parseAs, + getTrusted = sce.getTrusted, + trustAs = sce.trustAs; + + forEach(SCE_CONTEXTS, function (enumValue, name) { + var lName = lowercase(name); + sce[camelCase("parse_as_" + lName)] = function (expr) { + return parse(enumValue, expr); + }; + sce[camelCase("get_trusted_" + lName)] = function (value) { + return getTrusted(enumValue, value); + }; + sce[camelCase("trust_as_" + lName)] = function (value) { + return trustAs(enumValue, value); + }; + }); + + return sce; + }]; +} + +/** + * !!! This is an undocumented "private" service !!! + * + * @name ng.$sniffer + * @requires $window + * @requires $document + * + * @property {boolean} history Does the browser support html5 history api ? + * @property {boolean} hashchange Does the browser support hashchange event ? + * @property {boolean} transitions Does the browser support CSS transition events ? + * @property {boolean} animations Does the browser support CSS animation events ? + * + * @description + * This is very simple implementation of testing browser's features. + */ +function $SnifferProvider() { + this.$get = ['$window', '$document', function($window, $document) { + var eventSupport = {}, + android = + int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), + boxee = /Boxee/i.test(($window.navigator || {}).userAgent), + document = $document[0] || {}, + vendorPrefix, + vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, + bodyStyle = document.body && document.body.style, + transitions = false, + animations = false, + match; + + if (bodyStyle) { + for(var prop in bodyStyle) { + if(match = vendorRegex.exec(prop)) { + vendorPrefix = match[0]; + vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); + break; + } + } + + if(!vendorPrefix) { + vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; + } + + transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); + animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); + + if (android && (!transitions||!animations)) { + transitions = isString(document.body.style.webkitTransition); + animations = isString(document.body.style.webkitAnimation); + } + } + + + return { + // Android has history.pushState, but it does not update location correctly + // so let's not use the history API at all. + // http://code.google.com/p/android/issues/detail?id=17471 + // https://github.com/angular/angular.js/issues/904 + + // older webit browser (533.9) on Boxee box has exactly the same problem as Android has + // so let's not use the history API also + // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined + // jshint -W018 + history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), + // jshint +W018 + hashchange: 'onhashchange' in $window && + // IE8 compatible mode lies + (!document.documentMode || document.documentMode > 7), + hasEvent: function(event) { + // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have + // it. In particular the event is not fired when backspace or delete key are pressed or + // when cut operation is performed. + if (event == 'input' && msie == 9) return false; + + if (isUndefined(eventSupport[event])) { + var divElm = document.createElement('div'); + eventSupport[event] = 'on' + event in divElm; + } + + return eventSupport[event]; + }, + csp: csp(), + vendorPrefix: vendorPrefix, + transitions : transitions, + animations : animations, + msie : msie + }; + }]; +} + +function $TimeoutProvider() { + this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', + function($rootScope, $browser, $q, $exceptionHandler) { + var deferreds = {}; + + + /** + * @ngdoc function + * @name ng.$timeout + * @requires $browser + * + * @description + * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch + * block and delegates any exceptions to + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * The return value of registering a timeout function is a promise, which will be resolved when + * the timeout is reached and the timeout function is executed. + * + * To cancel a timeout request, call `$timeout.cancel(promise)`. + * + * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to + * synchronously flush the queue of deferred functions. + * + * @param {function()} fn A function, whose execution should be delayed. + * @param {number=} [delay=0] Delay in milliseconds. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. + * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this + * promise will be resolved with is the return value of the `fn` function. + * + * @example + + + + +
      +
      + Date format:
      + Current time is: +
      + Blood 1 : {{blood_1}} + Blood 2 : {{blood_2}} + + + +
      +
      + +
      +
      + */ + function timeout(fn, delay, invokeApply) { + var deferred = $q.defer(), + promise = deferred.promise, + skipApply = (isDefined(invokeApply) && !invokeApply), + timeoutId; + + timeoutId = $browser.defer(function() { + try { + deferred.resolve(fn()); + } catch(e) { + deferred.reject(e); + $exceptionHandler(e); + } + finally { + delete deferreds[promise.$$timeoutId]; + } + + if (!skipApply) $rootScope.$apply(); + }, delay); + + promise.$$timeoutId = timeoutId; + deferreds[timeoutId] = deferred; + + return promise; + } + + + /** + * @ngdoc function + * @name ng.$timeout#cancel + * @methodOf ng.$timeout + * + * @description + * Cancels a task associated with the `promise`. As a result of this, the promise will be + * resolved with a rejection. + * + * @param {Promise=} promise Promise returned by the `$timeout` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + timeout.cancel = function(promise) { + if (promise && promise.$$timeoutId in deferreds) { + deferreds[promise.$$timeoutId].reject('canceled'); + delete deferreds[promise.$$timeoutId]; + return $browser.defer.cancel(promise.$$timeoutId); + } + return false; + }; + + return timeout; + }]; +} + +// NOTE: The usage of window and document instead of $window and $document here is +// deliberate. This service depends on the specific behavior of anchor nodes created by the +// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and +// cause us to break tests. In addition, when the browser resolves a URL for XHR, it +// doesn't know about mocked locations and resolves URLs to the real document - which is +// exactly the behavior needed here. There is little value is mocking these out for this +// service. +var urlParsingNode = document.createElement("a"); +var originUrl = urlResolve(window.location.href, true); + +/** + * + * Implementation Notes for non-IE browsers + * ---------------------------------------- + * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, + * results both in the normalizing and parsing of the URL. Normalizing means that a relative + * URL will be resolved into an absolute URL in the context of the application document. + * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related + * properties are all populated to reflect the normalized URL. This approach has wide + * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * + * Implementation Notes for IE + * --------------------------- + * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other + * browsers. However, the parsed components will not be set if the URL assigned did not specify + * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We + * work around that by performing the parsing in a 2nd step by taking a previously normalized + * URL (e.g. by assining to a.href) and assigning it a.href again. This correctly populates the + * properties such as protocol, hostname, port, etc. + * + * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one + * uses the inner HTML approach to assign the URL as part of an HTML snippet - + * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. + * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. + * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that + * method and IE < 8 is unsupported. + * + * References: + * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement + * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html + * http://url.spec.whatwg.org/#urlutils + * https://github.com/angular/angular.js/pull/2902 + * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ + * + * @function + * @param {string} url The URL to be parsed. + * @description Normalizes and parses a URL. + * @returns {object} Returns the normalized URL as a dictionary. + * + * | member name | Description | + * |---------------|----------------| + * | href | A normalized version of the provided URL if it was not an absolute URL | + * | protocol | The protocol including the trailing colon | + * | host | The host and port (if the port is non-default) of the normalizedUrl | + * | search | The search params, minus the question mark | + * | hash | The hash string, minus the hash symbol + * | hostname | The hostname + * | port | The port, without ":" + * | pathname | The pathname, beginning with "/" + * + */ +function urlResolve(url) { + var href = url; + if (msie) { + // Normalize before parse. Refer Implementation Notes on why this is + // done in two steps on IE. + urlParsingNode.setAttribute("href", href); + href = urlParsingNode.href; + } + + urlParsingNode.setAttribute('href', href); + + // $$urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: urlParsingNode.pathname && urlParsingNode.pathname.charAt(0) === '/' ? + urlParsingNode.pathname : '/' + urlParsingNode.pathname + }; +} + + +/** + * Parse a request URL and determine whether this is a same-origin request as the application document. + * + * @param {string|object} requestUrl The url of the request as a string that will be resolved + * or a parsed URL object. + * @returns {boolean} Whether the request is for the same origin as the application document. + */ +function urlIsSameOrigin(requestUrl) { + var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; + return (parsed.protocol === originUrl.protocol && + parsed.host === originUrl.host); +} + +/** + * @ngdoc object + * @name ng.$window + * + * @description + * A reference to the browser's `window` object. While `window` + * is globally available in JavaScript, it causes testability problems, because + * it is a global variable. In angular we always refer to it through the + * `$window` service, so it may be overridden, removed or mocked for testing. + * + * Expressions, like the one defined for the `ngClick` directive in the example + * below, are evaluated with respect to the current scope. Therefore, there is + * no risk of inadvertently coding in a dependency on a global value in such an + * expression. + * + * @example + + + +
      + + +
      +
      + + it('should display the greeting in the input box', function() { + input('greeting').enter('Hello, E2E Tests'); + // If we click the button it will block the test runner + // element(':button').click(); + }); + +
      + */ +function $WindowProvider(){ + this.$get = valueFn(window); +} + +/** + * @ngdoc object + * @name ng.$filterProvider + * @description + * + * Filters are just functions which transform input to an output. However filters need to be + * Dependency Injected. To achieve this a filter definition consists of a factory function which is + * annotated with dependencies and is responsible for creating a filter function. + * + *
      + *   // Filter registration
      + *   function MyModule($provide, $filterProvider) {
      + *     // create a service to demonstrate injection (not always needed)
      + *     $provide.value('greet', function(name){
      + *       return 'Hello ' + name + '!';
      + *     });
      + *
      + *     // register a filter factory which uses the
      + *     // greet service to demonstrate DI.
      + *     $filterProvider.register('greet', function(greet){
      + *       // return the filter function which uses the greet service
      + *       // to generate salutation
      + *       return function(text) {
      + *         // filters need to be forgiving so check input validity
      + *         return text && greet(text) || text;
      + *       };
      + *     });
      + *   }
      + * 
      + * + * The filter function is registered with the `$injector` under the filter name suffix with + * `Filter`. + * + *
      + *   it('should be the same instance', inject(
      + *     function($filterProvider) {
      + *       $filterProvider.register('reverse', function(){
      + *         return ...;
      + *       });
      + *     },
      + *     function($filter, reverseFilter) {
      + *       expect($filter('reverse')).toBe(reverseFilter);
      + *     });
      + * 
      + * + * + * For more information about how angular filters work, and how to create your own filters, see + * {@link guide/filter Filters} in the Angular Developer Guide. + */ +/** + * @ngdoc method + * @name ng.$filterProvider#register + * @methodOf ng.$filterProvider + * @description + * Register filter factory function. + * + * @param {String} name Name of the filter. + * @param {function} fn The filter factory function which is injectable. + */ + + +/** + * @ngdoc function + * @name ng.$filter + * @function + * @description + * Filters are used for formatting data displayed to the user. + * + * The general syntax in templates is as follows: + * + * {{ expression [| filter_name[:parameter_value] ... ] }} + * + * @param {String} name Name of the filter function to retrieve + * @return {Function} the filter function + */ +$FilterProvider.$inject = ['$provide']; +function $FilterProvider($provide) { + var suffix = 'Filter'; + + /** + * @ngdoc function + * @name ng.$controllerProvider#register + * @methodOf ng.$controllerProvider + * @param {string|Object} name Name of the filter function, or an object map of filters where + * the keys are the filter names and the values are the filter factories. + * @returns {Object} Registered filter instance, or if a map of filters was provided then a map + * of the registered filter instances. + */ + function register(name, factory) { + if(isObject(name)) { + var filters = {}; + forEach(name, function(filter, key) { + filters[key] = register(key, filter); + }); + return filters; + } else { + return $provide.factory(name + suffix, factory); + } + } + this.register = register; + + this.$get = ['$injector', function($injector) { + return function(name) { + return $injector.get(name + suffix); + }; + }]; + + //////////////////////////////////////// + + /* global + currencyFilter: false, + dateFilter: false, + filterFilter: false, + jsonFilter: false, + limitToFilter: false, + lowercaseFilter: false, + numberFilter: false, + orderByFilter: false, + uppercaseFilter: false, + */ + + register('currency', currencyFilter); + register('date', dateFilter); + register('filter', filterFilter); + register('json', jsonFilter); + register('limitTo', limitToFilter); + register('lowercase', lowercaseFilter); + register('number', numberFilter); + register('orderBy', orderByFilter); + register('uppercase', uppercaseFilter); +} + +/** + * @ngdoc filter + * @name ng.filter:filter + * @function + * + * @description + * Selects a subset of items from `array` and returns it as a new array. + * + * @param {Array} array The source array. + * @param {string|Object|function()} expression The predicate to be used for selecting items from + * `array`. + * + * Can be one of: + * + * - `string`: Predicate that results in a substring match using the value of `expression` + * string. All strings or objects with string properties in `array` that contain this string + * will be returned. The predicate can be negated by prefixing the string with `!`. + * + * - `Object`: A pattern object can be used to filter specific properties on objects contained + * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items + * which have property `name` containing "M" and property `phone` containing "1". A special + * property name `$` can be used (as in `{$:"text"}`) to accept a match against any + * property of the object. That's equivalent to the simple substring match with a `string` + * as described above. + * + * - `function`: A predicate function can be used to write arbitrary filters. The function is + * called for each element of `array`. The final result is an array of those elements that + * the predicate returned true for. + * + * @param {function(expected, actual)|true|undefined} comparator Comparator which is used in + * determining if the expected value (from the filter expression) and actual value (from + * the object in the array) should be considered a match. + * + * Can be one of: + * + * - `function(expected, actual)`: + * The function will be given the object value and the predicate value to compare and + * should return true if the item should be included in filtered result. + * + * - `true`: A shorthand for `function(expected, actual) { return angular.equals(expected, actual)}`. + * this is essentially strict comparison of expected and actual. + * + * - `false|undefined`: A short hand for a function which will look for a substring match in case + * insensitive way. + * + * @example + + +
      + + Search: + + + + + + +
      NamePhone
      {{friend.name}}{{friend.phone}}
      +
      + Any:
      + Name only
      + Phone only
      + Equality
      + + + + + + +
      NamePhone
      {{friend.name}}{{friend.phone}}
      +
      + + it('should search across all fields when filtering with a string', function() { + input('searchText').enter('m'); + expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Mike', 'Adam']); + + input('searchText').enter('76'); + expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). + toEqual(['John', 'Julie']); + }); + + it('should search in specific fields when filtering with a predicate object', function() { + input('search.$').enter('i'); + expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Mike', 'Julie', 'Juliette']); + }); + it('should use a equal comparison when comparator is true', function() { + input('search.name').enter('Julie'); + input('strict').check(); + expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). + toEqual(['Julie']); + }); + +
      + */ +function filterFilter() { + return function(array, expression, comparator) { + if (!isArray(array)) return array; + + var comparatorType = typeof(comparator), + predicates = []; + + predicates.check = function(value) { + for (var j = 0; j < predicates.length; j++) { + if(!predicates[j](value)) { + return false; + } + } + return true; + }; + + if (comparatorType !== 'function') { + if (comparatorType === 'boolean' && comparator) { + comparator = function(obj, text) { + return angular.equals(obj, text); + }; + } else { + comparator = function(obj, text) { + text = (''+text).toLowerCase(); + return (''+obj).toLowerCase().indexOf(text) > -1; + }; + } + } + + var search = function(obj, text){ + if (typeof text == 'string' && text.charAt(0) === '!') { + return !search(obj, text.substr(1)); + } + switch (typeof obj) { + case "boolean": + case "number": + case "string": + return comparator(obj, text); + case "object": + switch (typeof text) { + case "object": + return comparator(obj, text); + default: + for ( var objKey in obj) { + if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { + return true; + } + } + break; + } + return false; + case "array": + for ( var i = 0; i < obj.length; i++) { + if (search(obj[i], text)) { + return true; + } + } + return false; + default: + return false; + } + }; + switch (typeof expression) { + case "boolean": + case "number": + case "string": + // Set up expression object and fall through + expression = {$:expression}; + // jshint -W086 + case "object": + // jshint +W086 + for (var key in expression) { + if (key == '$') { + (function() { + if (!expression[key]) return; + var path = key; + predicates.push(function(value) { + return search(value, expression[path]); + }); + })(); + } else { + (function() { + if (typeof(expression[key]) == 'undefined') { return; } + var path = key; + predicates.push(function(value) { + return search(getter(value,path), expression[path]); + }); + })(); + } + } + break; + case 'function': + predicates.push(expression); + break; + default: + return array; + } + var filtered = []; + for ( var j = 0; j < array.length; j++) { + var value = array[j]; + if (predicates.check(value)) { + filtered.push(value); + } + } + return filtered; + }; +} + +/** + * @ngdoc filter + * @name ng.filter:currency + * @function + * + * @description + * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default + * symbol for current locale is used. + * + * @param {number} amount Input to filter. + * @param {string=} symbol Currency symbol or identifier to be displayed. + * @returns {string} Formatted number. + * + * + * @example + + + +
      +
      + default currency symbol ($): {{amount | currency}}
      + custom currency identifier (USD$): {{amount | currency:"USD$"}} +
      +
      + + it('should init with 1234.56', function() { + expect(binding('amount | currency')).toBe('$1,234.56'); + expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56'); + }); + it('should update', function() { + input('amount').enter('-1234'); + expect(binding('amount | currency')).toBe('($1,234.00)'); + expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)'); + }); + +
      + */ +currencyFilter.$inject = ['$locale']; +function currencyFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(amount, currencySymbol){ + if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; + return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). + replace(/\u00A4/g, currencySymbol); + }; +} + +/** + * @ngdoc filter + * @name ng.filter:number + * @function + * + * @description + * Formats a number as text. + * + * If the input is not a number an empty string is returned. + * + * @param {number|string} number Number to format. + * @param {(number|string)=} fractionSize Number of decimal places to round the number to. + * If this is not provided then the fraction size is computed from the current locale's number + * formatting pattern. In the case of the default locale, it will be 3. + * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. + * + * @example + + + +
      + Enter number:
      + Default formatting: {{val | number}}
      + No fractions: {{val | number:0}}
      + Negative number: {{-val | number:4}} +
      +
      + + it('should format numbers', function() { + expect(binding('val | number')).toBe('1,234.568'); + expect(binding('val | number:0')).toBe('1,235'); + expect(binding('-val | number:4')).toBe('-1,234.5679'); + }); + + it('should update', function() { + input('val').enter('3374.333'); + expect(binding('val | number')).toBe('3,374.333'); + expect(binding('val | number:0')).toBe('3,374'); + expect(binding('-val | number:4')).toBe('-3,374.3330'); + }); + +
      + */ + + +numberFilter.$inject = ['$locale']; +function numberFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(number, fractionSize) { + return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, + fractionSize); + }; +} + +var DECIMAL_SEP = '.'; +function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { + if (isNaN(number) || !isFinite(number)) return ''; + + var isNegative = number < 0; + number = Math.abs(number); + var numStr = number + '', + formatedText = '', + parts = []; + + var hasExponent = false; + if (numStr.indexOf('e') !== -1) { + var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); + if (match && match[2] == '-' && match[3] > fractionSize + 1) { + numStr = '0'; + } else { + formatedText = numStr; + hasExponent = true; + } + } + + if (!hasExponent) { + var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; + + // determine fractionSize if it is not specified + if (isUndefined(fractionSize)) { + fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); + } + + var pow = Math.pow(10, fractionSize); + number = Math.round(number * pow) / pow; + var fraction = ('' + number).split(DECIMAL_SEP); + var whole = fraction[0]; + fraction = fraction[1] || ''; + + var i, pos = 0, + lgroup = pattern.lgSize, + group = pattern.gSize; + + if (whole.length >= (lgroup + group)) { + pos = whole.length - lgroup; + for (i = 0; i < pos; i++) { + if ((pos - i)%group === 0 && i !== 0) { + formatedText += groupSep; + } + formatedText += whole.charAt(i); + } + } + + for (i = pos; i < whole.length; i++) { + if ((whole.length - i)%lgroup === 0 && i !== 0) { + formatedText += groupSep; + } + formatedText += whole.charAt(i); + } + + // format fraction part. + while(fraction.length < fractionSize) { + fraction += '0'; + } + + if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); + } else { + + if (fractionSize > 0 && number > -1 && number < 1) { + formatedText = number.toFixed(fractionSize); + } + } + + parts.push(isNegative ? pattern.negPre : pattern.posPre); + parts.push(formatedText); + parts.push(isNegative ? pattern.negSuf : pattern.posSuf); + return parts.join(''); +} + +function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while(num.length < digits) num = '0' + num; + if (trim) + num = num.substr(num.length - digits); + return neg + num; +} + + +function dateGetter(name, size, offset, trim) { + offset = offset || 0; + return function(date) { + var value = date['get' + name](); + if (offset > 0 || value > -offset) + value += offset; + if (value === 0 && offset == -12 ) value = 12; + return padNumber(value, size, trim); + }; +} + +function dateStrGetter(name, shortForm) { + return function(date, formats) { + var value = date['get' + name](); + var get = uppercase(shortForm ? ('SHORT' + name) : name); + + return formats[get][value]; + }; +} + +function timeZoneGetter(date) { + var zone = -1 * date.getTimezoneOffset(); + var paddedZone = (zone >= 0) ? "+" : ""; + + paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + + padNumber(Math.abs(zone % 60), 2); + + return paddedZone; +} + +function ampmGetter(date, formats) { + return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; +} + +var DATE_FORMATS = { + yyyy: dateGetter('FullYear', 4), + yy: dateGetter('FullYear', 2, 0, true), + y: dateGetter('FullYear', 1), + MMMM: dateStrGetter('Month'), + MMM: dateStrGetter('Month', true), + MM: dateGetter('Month', 2, 1), + M: dateGetter('Month', 1, 1), + dd: dateGetter('Date', 2), + d: dateGetter('Date', 1), + HH: dateGetter('Hours', 2), + H: dateGetter('Hours', 1), + hh: dateGetter('Hours', 2, -12), + h: dateGetter('Hours', 1, -12), + mm: dateGetter('Minutes', 2), + m: dateGetter('Minutes', 1), + ss: dateGetter('Seconds', 2), + s: dateGetter('Seconds', 1), + // while ISO 8601 requires fractions to be prefixed with `.` or `,` + // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions + sss: dateGetter('Milliseconds', 3), + EEEE: dateStrGetter('Day'), + EEE: dateStrGetter('Day', true), + a: ampmGetter, + Z: timeZoneGetter +}; + +var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, + NUMBER_STRING = /^\-?\d+$/; + +/** + * @ngdoc filter + * @name ng.filter:date + * @function + * + * @description + * Formats `date` to a string based on the requested `format`. + * + * `format` string can be composed of the following elements: + * + * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) + * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) + * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) + * * `'MMMM'`: Month in year (January-December) + * * `'MMM'`: Month in year (Jan-Dec) + * * `'MM'`: Month in year, padded (01-12) + * * `'M'`: Month in year (1-12) + * * `'dd'`: Day in month, padded (01-31) + * * `'d'`: Day in month (1-31) + * * `'EEEE'`: Day in Week,(Sunday-Saturday) + * * `'EEE'`: Day in Week, (Sun-Sat) + * * `'HH'`: Hour in day, padded (00-23) + * * `'H'`: Hour in day (0-23) + * * `'hh'`: Hour in am/pm, padded (01-12) + * * `'h'`: Hour in am/pm, (1-12) + * * `'mm'`: Minute in hour, padded (00-59) + * * `'m'`: Minute in hour (0-59) + * * `'ss'`: Second in minute, padded (00-59) + * * `'s'`: Second in minute (0-59) + * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) + * * `'a'`: am/pm marker + * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) + * + * `format` string can also be one of the following predefined + * {@link guide/i18n localizable formats}: + * + * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale + * (e.g. Sep 3, 2010 12:05:08 pm) + * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) + * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale + * (e.g. Friday, September 3, 2010) + * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) + * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) + * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) + * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) + * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) + * + * `format` string can contain literal values. These need to be quoted with single quotes (e.g. + * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence + * (e.g. `"h 'o''clock'"`). + * + * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or + * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its + * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is + * specified in the string input, the time is considered to be in the local timezone. + * @param {string=} format Formatting rules (see Description). If not specified, + * `mediumDate` is used. + * @returns {string} Formatted string or the input if input is not recognized as date/millis. + * + * @example + + + {{1288323623006 | date:'medium'}}: + {{1288323623006 | date:'medium'}}
      + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
      + {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: + {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
      +
      + + it('should format date', function() { + expect(binding("1288323623006 | date:'medium'")). + toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); + expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). + toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); + expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). + toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); + }); + +
      + */ +dateFilter.$inject = ['$locale']; +function dateFilter($locale) { + + + var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; + // 1 2 3 4 5 6 7 8 9 10 11 + function jsonStringToDate(string) { + var match; + if (match = string.match(R_ISO8601_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0, + dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, + timeSetter = match[8] ? date.setUTCHours : date.setHours; + + if (match[9]) { + tzHour = int(match[9] + match[10]); + tzMin = int(match[9] + match[11]); + } + dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); + var h = int(match[4]||0) - tzHour; + var m = int(match[5]||0) - tzMin; + var s = int(match[6]||0); + var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); + timeSetter.call(date, h, m, s, ms); + return date; + } + return string; + } + + + return function(date, format) { + var text = '', + parts = [], + fn, match; + + format = format || 'mediumDate'; + format = $locale.DATETIME_FORMATS[format] || format; + if (isString(date)) { + if (NUMBER_STRING.test(date)) { + date = int(date); + } else { + date = jsonStringToDate(date); + } + } + + if (isNumber(date)) { + date = new Date(date); + } + + if (!isDate(date)) { + return date; + } + + while(format) { + match = DATE_FORMATS_SPLIT.exec(format); + if (match) { + parts = concat(parts, match, 1); + format = parts.pop(); + } else { + parts.push(format); + format = null; + } + } + + forEach(parts, function(value){ + fn = DATE_FORMATS[value]; + text += fn ? fn(date, $locale.DATETIME_FORMATS) + : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); + }); + + return text; + }; +} + + +/** + * @ngdoc filter + * @name ng.filter:json + * @function + * + * @description + * Allows you to convert a JavaScript object into JSON string. + * + * This filter is mostly useful for debugging. When using the double curly {{value}} notation + * the binding is automatically converted to JSON. + * + * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. + * @returns {string} JSON string. + * + * + * @example: + + +
      {{ {'name':'value'} | json }}
      +
      + + it('should jsonify filtered objects', function() { + expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/); + }); + +
      + * + */ +function jsonFilter() { + return function(object) { + return toJson(object, true); + }; +} + + +/** + * @ngdoc filter + * @name ng.filter:lowercase + * @function + * @description + * Converts string to lowercase. + * @see angular.lowercase + */ +var lowercaseFilter = valueFn(lowercase); + + +/** + * @ngdoc filter + * @name ng.filter:uppercase + * @function + * @description + * Converts string to uppercase. + * @see angular.uppercase + */ +var uppercaseFilter = valueFn(uppercase); + +/** + * @ngdoc function + * @name ng.filter:limitTo + * @function + * + * @description + * Creates a new array or string containing only a specified number of elements. The elements + * are taken from either the beginning or the end of the source array or string, as specified by + * the value and sign (positive or negative) of `limit`. + * + * @param {Array|string} input Source array or string to be limited. + * @param {string|number} limit The length of the returned array or string. If the `limit` number + * is positive, `limit` number of items from the beginning of the source array/string are copied. + * If the number is negative, `limit` number of items from the end of the source array/string + * are copied. The `limit` will be trimmed if it exceeds `array.length` + * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array + * had less than `limit` elements. + * + * @example + + + +
      + Limit {{numbers}} to: +

      Output numbers: {{ numbers | limitTo:numLimit }}

      + Limit {{letters}} to: +

      Output letters: {{ letters | limitTo:letterLimit }}

      +
      +
      + + it('should limit the number array to first three items', function() { + expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3'); + expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3'); + expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]'); + expect(binding('letters | limitTo:letterLimit')).toEqual('abc'); + }); + + it('should update the output when -3 is entered', function() { + input('numLimit').enter(-3); + input('letterLimit').enter(-3); + expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]'); + expect(binding('letters | limitTo:letterLimit')).toEqual('ghi'); + }); + + it('should not exceed the maximum size of input array', function() { + input('numLimit').enter(100); + input('letterLimit').enter(100); + expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]'); + expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi'); + }); + +
      + */ +function limitToFilter(){ + return function(input, limit) { + if (!isArray(input) && !isString(input)) return input; + + limit = int(limit); + + if (isString(input)) { + //NaN check on limit + if (limit) { + return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); + } else { + return ""; + } + } + + var out = [], + i, n; + + // if abs(limit) exceeds maximum length, trim it + if (limit > input.length) + limit = input.length; + else if (limit < -input.length) + limit = -input.length; + + if (limit > 0) { + i = 0; + n = limit; + } else { + i = input.length + limit; + n = input.length; + } + + for (; i} expression A predicate to be + * used by the comparator to determine the order of elements. + * + * Can be one of: + * + * - `function`: Getter function. The result of this function will be sorted using the + * `<`, `=`, `>` operator. + * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' + * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control + * ascending or descending sort order (for example, +name or -name). + * - `Array`: An array of function or string predicates. The first predicate in the array + * is used for sorting, but when two items are equivalent, the next predicate is used. + * + * @param {boolean=} reverse Reverse the order the array. + * @returns {Array} Sorted copy of the source array. + * + * @example + + + +
      +
      Sorting predicate = {{predicate}}; reverse = {{reverse}}
      +
      + [ unsorted ] + + + + + + + + + + + +
      Name + (^)Phone NumberAge
      {{friend.name}}{{friend.phone}}{{friend.age}}
      +
      +
      + + it('should be reverse ordered by aged', function() { + expect(binding('predicate')).toBe('-age'); + expect(repeater('table.friend', 'friend in friends').column('friend.age')). + toEqual(['35', '29', '21', '19', '10']); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); + }); + + it('should reorder the table when user selects different predicate', function() { + element('.doc-example-live a:contains("Name")').click(); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); + expect(repeater('table.friend', 'friend in friends').column('friend.age')). + toEqual(['35', '10', '29', '19', '21']); + + element('.doc-example-live a:contains("Phone")').click(); + expect(repeater('table.friend', 'friend in friends').column('friend.phone')). + toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); + }); + +
      + */ +orderByFilter.$inject = ['$parse']; +function orderByFilter($parse){ + return function(array, sortPredicate, reverseOrder) { + if (!isArray(array)) return array; + if (!sortPredicate) return array; + sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; + sortPredicate = map(sortPredicate, function(predicate){ + var descending = false, get = predicate || identity; + if (isString(predicate)) { + if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { + descending = predicate.charAt(0) == '-'; + predicate = predicate.substring(1); + } + get = $parse(predicate); + } + return reverseComparator(function(a,b){ + return compare(get(a),get(b)); + }, descending); + }); + var arrayCopy = []; + for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } + return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); + + function comparator(o1, o2){ + for ( var i = 0; i < sortPredicate.length; i++) { + var comp = sortPredicate[i](o1, o2); + if (comp !== 0) return comp; + } + return 0; + } + function reverseComparator(comp, descending) { + return toBoolean(descending) + ? function(a,b){return comp(b,a);} + : comp; + } + function compare(v1, v2){ + var t1 = typeof v1; + var t2 = typeof v2; + if (t1 == t2) { + if (t1 == "string") { + v1 = v1.toLowerCase(); + v2 = v2.toLowerCase(); + } + if (v1 === v2) return 0; + return v1 < v2 ? -1 : 1; + } else { + return t1 < t2 ? -1 : 1; + } + } + }; +} + +function ngDirective(directive) { + if (isFunction(directive)) { + directive = { + link: directive + }; + } + directive.restrict = directive.restrict || 'AC'; + return valueFn(directive); +} + +/** + * @ngdoc directive + * @name ng.directive:a + * @restrict E + * + * @description + * Modifies the default behavior of the html A tag so that the default action is prevented when + * the href attribute is empty. + * + * This change permits the easy creation of action links with the `ngClick` directive + * without changing the location or causing page reloads, e.g.: + * `Add Item` + */ +var htmlAnchorDirective = valueFn({ + restrict: 'E', + compile: function(element, attr) { + + if (msie <= 8) { + + // turn link into a stylable link in IE + // but only if it doesn't have name attribute, in which case it's an anchor + if (!attr.href && !attr.name) { + attr.$set('href', ''); + } + + // add a comment node to anchors to workaround IE bug that causes element content to be reset + // to new attribute content if attribute is updated with value containing @ and element also + // contains value with @ + // see issue #1949 + element.append(document.createComment('IE fix')); + } + + return function(scope, element) { + element.on('click', function(event){ + // if we have no href url, then don't navigate anywhere. + if (!element.attr('href')) { + event.preventDefault(); + } + }); + }; + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngHref + * @restrict A + * + * @description + * Using Angular markup like `{{hash}}` in an href attribute will + * make the link go to the wrong URL if the user clicks it before + * Angular has a chance to replace the `{{hash}}` markup with its + * value. Until Angular replaces the markup the link will be broken + * and will most likely return a 404 error. + * + * The `ngHref` directive solves this problem. + * + * The wrong way to write it: + *
      + * 
      + * 
      + * + * The correct way to write it: + *
      + * 
      + * 
      + * + * @element A + * @param {template} ngHref any string which can contain `{{}}` markup. + * + * @example + * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes + * in links and their different behaviors: + + +
      +
      link 1 (link, don't reload)
      + link 2 (link, don't reload)
      + link 3 (link, reload!)
      + anchor (link, don't reload)
      + anchor (no link)
      + link (link, change location) + + + it('should execute ng-click but not reload when href without value', function() { + element('#link-1').click(); + expect(input('value').val()).toEqual('1'); + expect(element('#link-1').attr('href')).toBe(""); + }); + + it('should execute ng-click but not reload when href empty string', function() { + element('#link-2').click(); + expect(input('value').val()).toEqual('2'); + expect(element('#link-2').attr('href')).toBe(""); + }); + + it('should execute ng-click and change url when ng-href specified', function() { + expect(element('#link-3').attr('href')).toBe("/123"); + + element('#link-3').click(); + expect(browser().window().path()).toEqual('/123'); + }); + + it('should execute ng-click but not reload when href empty string and name specified', function() { + element('#link-4').click(); + expect(input('value').val()).toEqual('4'); + expect(element('#link-4').attr('href')).toBe(''); + }); + + it('should execute ng-click but not reload when no href but name specified', function() { + element('#link-5').click(); + expect(input('value').val()).toEqual('5'); + expect(element('#link-5').attr('href')).toBe(undefined); + }); + + it('should only change url when only ng-href', function() { + input('value').enter('6'); + expect(element('#link-6').attr('href')).toBe('6'); + + element('#link-6').click(); + expect(browser().location().url()).toEqual('/6'); + }); + + + */ + +/** + * @ngdoc directive + * @name ng.directive:ngSrc + * @restrict A + * + * @description + * Using Angular markup like `{{hash}}` in a `src` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrc` directive solves this problem. + * + * The buggy way to write it: + *
      + * 
      + * 
      + * + * The correct way to write it: + *
      + * 
      + * 
      + * + * @element IMG + * @param {template} ngSrc any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ng.directive:ngSrcset + * @restrict A + * + * @description + * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrcset` directive solves this problem. + * + * The buggy way to write it: + *
      + * 
      + * 
      + * + * The correct way to write it: + *
      + * 
      + * 
      + * + * @element IMG + * @param {template} ngSrcset any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ng.directive:ngDisabled + * @restrict A + * + * @description + * + * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: + *
      + * 
      + * + *
      + *
      + * + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as disabled. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngDisabled` directive solves this problem for the `disabled` attribute. + * + * @example + + + Click me to toggle:
      + +
      + + it('should toggle button', function() { + expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy(); + input('checked').check(); + expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy(); + }); + +
      + * + * @element INPUT + * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, + * then special attribute "disabled" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngChecked + * @restrict A + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as checked. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngChecked` directive solves this problem for the `checked` attribute. + * @example + + + Check me to check both:
      + +
      + + it('should check both checkBoxes', function() { + expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy(); + input('master').check(); + expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy(); + }); + +
      + * + * @element INPUT + * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, + * then special attribute "checked" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngReadonly + * @restrict A + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as readonly. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngReadonly` directive solves this problem for the `readonly` attribute. + * @example + + + Check me to make text readonly:
      + +
      + + it('should toggle readonly attr', function() { + expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy(); + input('checked').check(); + expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy(); + }); + +
      + * + * @element INPUT + * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, + * then special attribute "readonly" will be set on the element + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngSelected + * @restrict A + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as selected. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngSelected` directive solves this problem for the `selected` atttribute. + * @example + + + Check me to select:
      + +
      + + it('should select Greetings!', function() { + expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy(); + input('selected').check(); + expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy(); + }); + +
      + * + * @element OPTION + * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, + * then special attribute "selected" will be set on the element + */ + +/** + * @ngdoc directive + * @name ng.directive:ngOpen + * @restrict A + * + * @description + * The HTML specification does not require browsers to preserve the values of boolean attributes + * such as open. (Their presence means true and their absence means false.) + * This prevents the Angular compiler from retrieving the binding expression. + * The `ngOpen` directive solves this problem for the `open` attribute. + * + * @example + + + Check me check multiple:
      +
      + Show/Hide me +
      +
      + + it('should toggle open', function() { + expect(element('#details').prop('open')).toBeFalsy(); + input('open').check(); + expect(element('#details').prop('open')).toBeTruthy(); + }); + +
      + * + * @element DETAILS + * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, + * then special attribute "open" will be set on the element + */ + +var ngAttributeAliasDirectives = {}; + + +// boolean attrs are evaluated +forEach(BOOLEAN_ATTR, function(propName, attrName) { + // binding to multiple is not supported + if (propName == "multiple") return; + + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 100, + compile: function() { + return function(scope, element, attr) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { + attr.$set(attrName, !!value); + }); + }; + } + }; + }; +}); + + +// ng-src, ng-srcset, ng-href are interpolated +forEach(['src', 'srcset', 'href'], function(attrName) { + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 99, // it needs to run after the attributes are interpolated + link: function(scope, element, attr) { + attr.$observe(normalized, function(value) { + if (!value) + return; + + attr.$set(attrName, value); + + // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist + // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need + // to set the property as well to achieve the desired effect. + // we use attr[attrName] value since $set can sanitize the url. + if (msie) element.prop(attrName, attr[attrName]); + }); + } + }; + }; +}); + +/* global -nullFormCtrl */ +var nullFormCtrl = { + $addControl: noop, + $removeControl: noop, + $setValidity: noop, + $setDirty: noop, + $setPristine: noop +}; + +/** + * @ngdoc object + * @name ng.directive:form.FormController + * + * @property {boolean} $pristine True if user has not interacted with the form yet. + * @property {boolean} $dirty True if user has already interacted with the form. + * @property {boolean} $valid True if all of the containing forms and controls are valid. + * @property {boolean} $invalid True if at least one containing control or form is invalid. + * + * @property {Object} $error Is an object hash, containing references to all invalid controls or + * forms, where: + * + * - keys are validation tokens (error names) — such as `required`, `url` or `email`), + * - values are arrays of controls or forms that are invalid with given error. + * + * @description + * `FormController` keeps track of all its controls and nested forms as well as state of them, + * such as being valid/invalid or dirty/pristine. + * + * Each {@link ng.directive:form form} directive creates an instance + * of `FormController`. + * + */ +//asks for $scope to fool the BC controller module +FormController.$inject = ['$element', '$attrs', '$scope']; +function FormController(element, attrs) { + var form = this, + parentForm = element.parent().controller('form') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + errors = form.$error = {}, + controls = []; + + // init state + form.$name = attrs.name || attrs.ngForm; + form.$dirty = false; + form.$pristine = true; + form.$valid = true; + form.$invalid = false; + + parentForm.$addControl(form); + + // Setup initial state of the control + element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + element. + removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). + addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$addControl + * @methodOf ng.directive:form.FormController + * + * @description + * Register a control with the form. + * + * Input elements using ngModelController do this automatically when they are linked. + */ + form.$addControl = function(control) { + // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored + // and not added to the scope. Now we throw an error. + assertNotHasOwnProperty(control.$name, 'input'); + controls.push(control); + + if (control.$name) { + form[control.$name] = control; + } + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$removeControl + * @methodOf ng.directive:form.FormController + * + * @description + * Deregister a control from the form. + * + * Input elements using ngModelController do this automatically when they are destroyed. + */ + form.$removeControl = function(control) { + if (control.$name && form[control.$name] === control) { + delete form[control.$name]; + } + forEach(errors, function(queue, validationToken) { + form.$setValidity(validationToken, true, control); + }); + + arrayRemove(controls, control); + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setValidity + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the validity of a form control. + * + * This method will also propagate to parent forms. + */ + form.$setValidity = function(validationToken, isValid, control) { + var queue = errors[validationToken]; + + if (isValid) { + if (queue) { + arrayRemove(queue, control); + if (!queue.length) { + invalidCount--; + if (!invalidCount) { + toggleValidCss(isValid); + form.$valid = true; + form.$invalid = false; + } + errors[validationToken] = false; + toggleValidCss(true, validationToken); + parentForm.$setValidity(validationToken, true, form); + } + } + + } else { + if (!invalidCount) { + toggleValidCss(isValid); + } + if (queue) { + if (includes(queue, control)) return; + } else { + errors[validationToken] = queue = []; + invalidCount++; + toggleValidCss(false, validationToken); + parentForm.$setValidity(validationToken, false, form); + } + queue.push(control); + + form.$valid = false; + form.$invalid = true; + } + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setDirty + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the form to a dirty state. + * + * This method can be called to add the 'ng-dirty' class and set the form to a dirty + * state (ng-dirty class). This method will also propagate to parent forms. + */ + form.$setDirty = function() { + element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); + form.$dirty = true; + form.$pristine = false; + parentForm.$setDirty(); + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setPristine + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the form to its pristine state. + * + * This method can be called to remove the 'ng-dirty' class and set the form to its pristine + * state (ng-pristine class). This method will also propagate to all the controls contained + * in this form. + * + * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after + * saving or resetting it. + */ + form.$setPristine = function () { + element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); + form.$dirty = false; + form.$pristine = true; + forEach(controls, function(control) { + control.$setPristine(); + }); + }; +} + + +/** + * @ngdoc directive + * @name ng.directive:ngForm + * @restrict EAC + * + * @description + * Nestable alias of {@link ng.directive:form `form`} directive. HTML + * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a + * sub-group of controls needs to be determined. + * + * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + */ + + /** + * @ngdoc directive + * @name ng.directive:form + * @restrict E + * + * @description + * Directive that instantiates + * {@link ng.directive:form.FormController FormController}. + * + * If the `name` attribute is specified, the form controller is published onto the current scope under + * this name. + * + * # Alias: {@link ng.directive:ngForm `ngForm`} + * + * In Angular forms can be nested. This means that the outer form is valid when all of the child + * forms are valid as well. However, browsers do not allow nesting of `
      ` elements, so + * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to + * `` but can be nested. This allows you to have nested forms, which is very useful when + * using Angular validation directives in forms that are dynamically generated using the + * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` + * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an + * `ngForm` directive and nest these in an outer `form` element. + * + * + * # CSS classes + * - `ng-valid` Is set if the form is valid. + * - `ng-invalid` Is set if the form is invalid. + * - `ng-pristine` Is set if the form is pristine. + * - `ng-dirty` Is set if the form is dirty. + * + * + * # Submitting a form and preventing the default action + * + * Since the role of forms in client-side Angular applications is different than in classical + * roundtrip apps, it is desirable for the browser not to translate the form submission into a full + * page reload that sends the data to the server. Instead some javascript logic should be triggered + * to handle the form submission in an application-specific way. + * + * For this reason, Angular prevents the default action (form submission to the server) unless the + * `` element has an `action` attribute specified. + * + * You can use one of the following two ways to specify what javascript method should be called when + * a form is submitted: + * + * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element + * - {@link ng.directive:ngClick ngClick} directive on the first + * button or input field of type submit (input[type=submit]) + * + * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} + * or {@link ng.directive:ngClick ngClick} directives. + * This is because of the following form submission rules in the HTML specification: + * + * - If a form has only one input field then hitting enter in this field triggers form submit + * (`ngSubmit`) + * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter + * doesn't trigger submit + * - if a form has one or more input fields and one or more buttons or input[type=submit] then + * hitting enter in any of the input fields will trigger the click handler on the *first* button or + * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) + * + * @param {string=} name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + * @example + + + + + userType: + Required!
      + userType = {{userType}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      + +
      + + it('should initialize to model', function() { + expect(binding('userType')).toEqual('guest'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('userType').enter(''); + expect(binding('userType')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
      + */ +var formDirectiveFactory = function(isNgForm) { + return ['$timeout', function($timeout) { + var formDirective = { + name: 'form', + restrict: isNgForm ? 'EAC' : 'E', + controller: FormController, + compile: function() { + return { + pre: function(scope, formElement, attr, controller) { + if (!attr.action) { + // we can't use jq events because if a form is destroyed during submission the default + // action is not prevented. see #1238 + // + // IE 9 is not affected because it doesn't fire a submit event and try to do a full + // page reload if the form was destroyed by submission of the form via a click handler + // on a button in the form. Looks like an IE9 specific bug. + var preventDefaultListener = function(event) { + event.preventDefault + ? event.preventDefault() + : event.returnValue = false; // IE + }; + + addEventListenerFn(formElement[0], 'submit', preventDefaultListener); + + // unregister the preventDefault listener so that we don't not leak memory but in a + // way that will achieve the prevention of the default action. + formElement.on('$destroy', function() { + $timeout(function() { + removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); + }, 0, false); + }); + } + + var parentFormCtrl = formElement.parent().controller('form'), + alias = attr.name || attr.ngForm; + + if (alias) { + setter(scope, alias, controller, alias); + } + if (parentFormCtrl) { + formElement.on('$destroy', function() { + parentFormCtrl.$removeControl(controller); + if (alias) { + setter(scope, alias, undefined, alias); + } + extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards + }); + } + } + }; + } + }; + + return formDirective; + }]; +}; + +var formDirective = formDirectiveFactory(); +var ngFormDirective = formDirectiveFactory(true); + +/* global + + -VALID_CLASS, + -INVALID_CLASS, + -PRISTINE_CLASS, + -DIRTY_CLASS +*/ + +var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; +var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/; +var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; + +var inputType = { + + /** + * @ngdoc inputType + * @name ng.directive:input.text + * + * @description + * Standard HTML text input with angular data binding. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Adds `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. + * + * @example + + + +
      + Single word: + + Required! + + Single word only! + + text = {{text}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('text')).toEqual('guest'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if multi word', function() { + input('text').enter('hello world'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should not be trimmed', function() { + input('text').enter('untrimmed '); + expect(binding('text')).toEqual('untrimmed '); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + +
      + */ + 'text': textInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.number + * + * @description + * Text input with number validation and transformation. Sets the `number` validation + * error if not a valid number. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + Number: + + Required! + + Not valid number! + value = {{value}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('value')).toEqual('12'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('value').enter(''); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if over max', function() { + input('value').enter('123'); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
      + */ + 'number': numberInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.url + * + * @description + * Text input with URL validation. Sets the `url` validation error key if the content is not a + * valid URL. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + URL: + + Required! + + Not valid url! + text = {{text}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      + myForm.$error.url = {{!!myForm.$error.url}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('text')).toEqual('http://google.com'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if not url', function() { + input('text').enter('xxx'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
      + */ + 'url': urlInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.email + * + * @description + * Text input with email validation. Sets the `email` validation error key if not a valid email + * address. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + Email: + + Required! + + Not valid email! + text = {{text}}
      + myForm.input.$valid = {{myForm.input.$valid}}
      + myForm.input.$error = {{myForm.input.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      + myForm.$error.email = {{!!myForm.$error.email}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('text')).toEqual('me@example.com'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if not email', function() { + input('text').enter('xxx'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
      + */ + 'email': emailInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.radio + * + * @description + * HTML radio button. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} value The value to which the expression should be set when selected. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + Red
      + Green
      + Blue
      + color = {{color}}
      +
      +
      + + it('should change state', function() { + expect(binding('color')).toEqual('blue'); + + input('color').select('red'); + expect(binding('color')).toEqual('red'); + }); + +
      + */ + 'radio': radioInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.checkbox + * + * @description + * HTML checkbox. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngTrueValue The value to which the expression should be set when selected. + * @param {string=} ngFalseValue The value to which the expression should be set when not selected. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      + Value1:
      + Value2:
      + value1 = {{value1}}
      + value2 = {{value2}}
      +
      +
      + + it('should change state', function() { + expect(binding('value1')).toEqual('true'); + expect(binding('value2')).toEqual('YES'); + + input('value1').check(); + input('value2').check(); + expect(binding('value1')).toEqual('false'); + expect(binding('value2')).toEqual('NO'); + }); + +
      + */ + 'checkbox': checkboxInputType, + + 'hidden': noop, + 'button': noop, + 'submit': noop, + 'reset': noop +}; + + +function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { + + var listener = function() { + var value = element.val(); + + // By default we will trim the value + // If the attribute ng-trim exists we will avoid trimming + // e.g. + if (toBoolean(attr.ngTrim || 'T')) { + value = trim(value); + } + + if (ctrl.$viewValue !== value) { + scope.$apply(function() { + ctrl.$setViewValue(value); + }); + } + }; + + // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the + // input event on backspace, delete or cut + if ($sniffer.hasEvent('input')) { + element.on('input', listener); + } else { + var timeout; + + var deferListener = function() { + if (!timeout) { + timeout = $browser.defer(function() { + listener(); + timeout = null; + }); + } + }; + + element.on('keydown', function(event) { + var key = event.keyCode; + + // ignore + // command modifiers arrows + if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; + + deferListener(); + }); + + // if user paste into input using mouse, we need "change" event to catch it + element.on('change', listener); + + // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it + if ($sniffer.hasEvent('paste')) { + element.on('paste cut', deferListener); + } + } + + + ctrl.$render = function() { + element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); + }; + + // pattern validator + var pattern = attr.ngPattern, + patternValidator, + match; + + var validate = function(regexp, value) { + if (ctrl.$isEmpty(value) || regexp.test(value)) { + ctrl.$setValidity('pattern', true); + return value; + } else { + ctrl.$setValidity('pattern', false); + return undefined; + } + }; + + if (pattern) { + match = pattern.match(/^\/(.*)\/([gim]*)$/); + if (match) { + pattern = new RegExp(match[1], match[2]); + patternValidator = function(value) { + return validate(pattern, value); + }; + } else { + patternValidator = function(value) { + var patternObj = scope.$eval(pattern); + + if (!patternObj || !patternObj.test) { + throw minErr('ngPattern')('noregexp', + 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, + patternObj, startingTag(element)); + } + return validate(patternObj, value); + }; + } + + ctrl.$formatters.push(patternValidator); + ctrl.$parsers.push(patternValidator); + } + + // min length validator + if (attr.ngMinlength) { + var minlength = int(attr.ngMinlength); + var minLengthValidator = function(value) { + if (!ctrl.$isEmpty(value) && value.length < minlength) { + ctrl.$setValidity('minlength', false); + return undefined; + } else { + ctrl.$setValidity('minlength', true); + return value; + } + }; + + ctrl.$parsers.push(minLengthValidator); + ctrl.$formatters.push(minLengthValidator); + } + + // max length validator + if (attr.ngMaxlength) { + var maxlength = int(attr.ngMaxlength); + var maxLengthValidator = function(value) { + if (!ctrl.$isEmpty(value) && value.length > maxlength) { + ctrl.$setValidity('maxlength', false); + return undefined; + } else { + ctrl.$setValidity('maxlength', true); + return value; + } + }; + + ctrl.$parsers.push(maxLengthValidator); + ctrl.$formatters.push(maxLengthValidator); + } +} + +function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + ctrl.$parsers.push(function(value) { + var empty = ctrl.$isEmpty(value); + if (empty || NUMBER_REGEXP.test(value)) { + ctrl.$setValidity('number', true); + return value === '' ? null : (empty ? value : parseFloat(value)); + } else { + ctrl.$setValidity('number', false); + return undefined; + } + }); + + ctrl.$formatters.push(function(value) { + return ctrl.$isEmpty(value) ? '' : '' + value; + }); + + if (attr.min) { + var minValidator = function(value) { + var min = parseFloat(attr.min); + if (!ctrl.$isEmpty(value) && value < min) { + ctrl.$setValidity('min', false); + return undefined; + } else { + ctrl.$setValidity('min', true); + return value; + } + }; + + ctrl.$parsers.push(minValidator); + ctrl.$formatters.push(minValidator); + } + + if (attr.max) { + var maxValidator = function(value) { + var max = parseFloat(attr.max); + if (!ctrl.$isEmpty(value) && value > max) { + ctrl.$setValidity('max', false); + return undefined; + } else { + ctrl.$setValidity('max', true); + return value; + } + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + + ctrl.$formatters.push(function(value) { + + if (ctrl.$isEmpty(value) || isNumber(value)) { + ctrl.$setValidity('number', true); + return value; + } else { + ctrl.$setValidity('number', false); + return undefined; + } + }); +} + +function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var urlValidator = function(value) { + if (ctrl.$isEmpty(value) || URL_REGEXP.test(value)) { + ctrl.$setValidity('url', true); + return value; + } else { + ctrl.$setValidity('url', false); + return undefined; + } + }; + + ctrl.$formatters.push(urlValidator); + ctrl.$parsers.push(urlValidator); +} + +function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var emailValidator = function(value) { + if (ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value)) { + ctrl.$setValidity('email', true); + return value; + } else { + ctrl.$setValidity('email', false); + return undefined; + } + }; + + ctrl.$formatters.push(emailValidator); + ctrl.$parsers.push(emailValidator); +} + +function radioInputType(scope, element, attr, ctrl) { + // make the name unique, if not defined + if (isUndefined(attr.name)) { + element.attr('name', nextUid()); + } + + element.on('click', function() { + if (element[0].checked) { + scope.$apply(function() { + ctrl.$setViewValue(attr.value); + }); + } + }); + + ctrl.$render = function() { + var value = attr.value; + element[0].checked = (value == ctrl.$viewValue); + }; + + attr.$observe('value', ctrl.$render); +} + +function checkboxInputType(scope, element, attr, ctrl) { + var trueValue = attr.ngTrueValue, + falseValue = attr.ngFalseValue; + + if (!isString(trueValue)) trueValue = true; + if (!isString(falseValue)) falseValue = false; + + element.on('click', function() { + scope.$apply(function() { + ctrl.$setViewValue(element[0].checked); + }); + }); + + ctrl.$render = function() { + element[0].checked = ctrl.$viewValue; + }; + + // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. + ctrl.$isEmpty = function(value) { + return value !== trueValue; + }; + + ctrl.$formatters.push(function(value) { + return value === trueValue; + }); + + ctrl.$parsers.push(function(value) { + return value ? trueValue : falseValue; + }); +} + + +/** + * @ngdoc directive + * @name ng.directive:textarea + * @restrict E + * + * @description + * HTML textarea element control with angular data-binding. The data-binding and validation + * properties of this element are exactly the same as those of the + * {@link ng.directive:input input element}. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + */ + + +/** + * @ngdoc directive + * @name ng.directive:input + * @restrict E + * + * @description + * HTML input element control with angular data-binding. Input control follows HTML5 input types + * and polyfills the HTML5 validation behavior for older browsers. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {boolean=} ngRequired Sets `required` attribute if set to true + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
      +
      + User name: + + Required!
      + Last name: + + Too short! + + Too long!
      +
      +
      + user = {{user}}
      + myForm.userName.$valid = {{myForm.userName.$valid}}
      + myForm.userName.$error = {{myForm.userName.$error}}
      + myForm.lastName.$valid = {{myForm.lastName.$valid}}
      + myForm.lastName.$error = {{myForm.lastName.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      + myForm.$error.minlength = {{!!myForm.$error.minlength}}
      + myForm.$error.maxlength = {{!!myForm.$error.maxlength}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}'); + expect(binding('myForm.userName.$valid')).toEqual('true'); + expect(binding('myForm.$valid')).toEqual('true'); + }); + + it('should be invalid if empty when required', function() { + input('user.name').enter(''); + expect(binding('user')).toEqual('{"last":"visitor"}'); + expect(binding('myForm.userName.$valid')).toEqual('false'); + expect(binding('myForm.$valid')).toEqual('false'); + }); + + it('should be valid if empty when min length is set', function() { + input('user.last').enter(''); + expect(binding('user')).toEqual('{"name":"guest","last":""}'); + expect(binding('myForm.lastName.$valid')).toEqual('true'); + expect(binding('myForm.$valid')).toEqual('true'); + }); + + it('should be invalid if less than required min length', function() { + input('user.last').enter('xx'); + expect(binding('user')).toEqual('{"name":"guest"}'); + expect(binding('myForm.lastName.$valid')).toEqual('false'); + expect(binding('myForm.lastName.$error')).toMatch(/minlength/); + expect(binding('myForm.$valid')).toEqual('false'); + }); + + it('should be invalid if longer than max length', function() { + input('user.last').enter('some ridiculously long name'); + expect(binding('user')) + .toEqual('{"name":"guest"}'); + expect(binding('myForm.lastName.$valid')).toEqual('false'); + expect(binding('myForm.lastName.$error')).toMatch(/maxlength/); + expect(binding('myForm.$valid')).toEqual('false'); + }); + +
      + */ +var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { + return { + restrict: 'E', + require: '?ngModel', + link: function(scope, element, attr, ctrl) { + if (ctrl) { + (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, + $browser); + } + } + }; +}]; + +var VALID_CLASS = 'ng-valid', + INVALID_CLASS = 'ng-invalid', + PRISTINE_CLASS = 'ng-pristine', + DIRTY_CLASS = 'ng-dirty'; + +/** + * @ngdoc object + * @name ng.directive:ngModel.NgModelController + * + * @property {string} $viewValue Actual string value in the view. + * @property {*} $modelValue The value in the model, that the control is bound to. + * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever + the control reads value from the DOM. Each function is called, in turn, passing the value + through to the next. Used to sanitize / convert the value as well as validation. + For validation, the parsers should update the validity state using + {@link ng.directive:ngModel.NgModelController#methods_$setValidity $setValidity()}, + and return `undefined` for invalid values. + + * + * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever + the model value changes. Each function is called, in turn, passing the value through to the + next. Used to format / convert values for display in the control and validation. + *
      + *      function formatter(value) {
      + *        if (value) {
      + *          return value.toUpperCase();
      + *        }
      + *      }
      + *      ngModel.$formatters.push(formatter);
      + *      
      + * @property {Object} $error An object hash with all errors as keys. + * + * @property {boolean} $pristine True if user has not interacted with the control yet. + * @property {boolean} $dirty True if user has already interacted with the control. + * @property {boolean} $valid True if there is no error. + * @property {boolean} $invalid True if at least one error on the control. + * + * @description + * + * `NgModelController` provides API for the `ng-model` directive. The controller contains + * services for data-binding, validation, CSS updates, and value formatting and parsing. It + * purposefully does not contain any logic which deals with DOM rendering or listening to + * DOM events. Such DOM related logic should be provided by other directives which make use of + * `NgModelController` for data-binding. + * + * ## Custom Control Example + * This example shows how to use `NgModelController` with a custom control to achieve + * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) + * collaborate together to achieve the desired result. + * + * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element + * contents be edited in place by the user. This will not work on older browsers. + * + * + + [contenteditable] { + border: 1px solid black; + background-color: white; + min-height: 20px; + } + + .ng-invalid { + border: 1px solid red; + } + + + + angular.module('customControl', []). + directive('contenteditable', function() { + return { + restrict: 'A', // only activate on element attribute + require: '?ngModel', // get a hold of NgModelController + link: function(scope, element, attrs, ngModel) { + if(!ngModel) return; // do nothing if no ng-model + + // Specify how UI should be updated + ngModel.$render = function() { + element.html(ngModel.$viewValue || ''); + }; + + // Listen for change events to enable binding + element.on('blur keyup change', function() { + scope.$apply(read); + }); + read(); // initialize + + // Write data to the model + function read() { + var html = element.html(); + // When we clear the content editable the browser leaves a
      behind + // If strip-br attribute is provided then we strip this out + if( attrs.stripBr && html == '
      ' ) { + html = ''; + } + ngModel.$setViewValue(html); + } + } + }; + }); +
      + +
      +
      Change me!
      + Required! +
      + +
      +
      + + it('should data-bind and become invalid', function() { + var contentEditable = element('[contenteditable]'); + + expect(contentEditable.text()).toEqual('Change me!'); + input('userContent').enter(''); + expect(contentEditable.text()).toEqual(''); + expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/); + }); + + *
      + * + * ## Isolated Scope Pitfall + * + * Note that if you have a directive with an isolated scope, you cannot require `ngModel` + * since the model value will be looked up on the isolated scope rather than the outer scope. + * When the directive updates the model value, calling `ngModel.$setViewValue()` the property + * on the outer scope will not be updated. However you can get around this by using $parent. + * + * Here is an example of this situation. You'll notice that the first div is not updating the input. + * However the second div can update the input properly. + * + * + + angular.module('badIsolatedDirective', []).directive('isolate', function() { + return { + require: 'ngModel', + scope: { }, + template: '', + link: function(scope, element, attrs, ngModel) { + scope.$watch('innerModel', function(value) { + console.log(value); + ngModel.$setViewValue(value); + }); + } + }; + }); + + + +
      +
      +
      + *
      + * + * + */ +var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', + function($scope, $exceptionHandler, $attr, $element, $parse) { + this.$viewValue = Number.NaN; + this.$modelValue = Number.NaN; + this.$parsers = []; + this.$formatters = []; + this.$viewChangeListeners = []; + this.$pristine = true; + this.$dirty = false; + this.$valid = true; + this.$invalid = false; + this.$name = $attr.name; + + var ngModelGet = $parse($attr.ngModel), + ngModelSet = ngModelGet.assign; + + if (!ngModelSet) { + throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", + $attr.ngModel, startingTag($element)); + } + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$render + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Called when the view needs to be updated. It is expected that the user of the ng-model + * directive will implement this method. + */ + this.$render = noop; + + /** + * @ngdoc function + * @name { ng.directive:ngModel.NgModelController#$isEmpty + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * This is called when we need to determine if the value of the input is empty. + * + * For instance, the required directive does this to work out if the input has data or not. + * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. + * + * You can override this for input directives whose concept of being empty is different to the + * default. The `checkboxInputType` directive does this because in its case a value of `false` + * implies empty. + */ + this.$isEmpty = function(value) { + return isUndefined(value) || value === '' || value === null || value !== value; + }; + + var parentForm = $element.inheritedData('$formController') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + $error = this.$error = {}; // keep invalid keys here + + + // Setup initial state of the control + $element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + $element. + removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). + addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setValidity + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Change the validity state, and notifies the form when the control changes validity. (i.e. it + * does not notify form if given validator is already marked as invalid). + * + * This method should be called by validators - i.e. the parser or formatter functions. + * + * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign + * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. + * The `validationErrorKey` should be in camelCase and will get converted into dash-case + * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` + * class and can be bound to as `{{someForm.someControl.$error.myError}}` . + * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). + */ + this.$setValidity = function(validationErrorKey, isValid) { + // Purposeful use of ! here to cast isValid to boolean in case it is undefined + // jshint -W018 + if ($error[validationErrorKey] === !isValid) return; + // jshint +W018 + + if (isValid) { + if ($error[validationErrorKey]) invalidCount--; + if (!invalidCount) { + toggleValidCss(true); + this.$valid = true; + this.$invalid = false; + } + } else { + toggleValidCss(false); + this.$invalid = true; + this.$valid = false; + invalidCount++; + } + + $error[validationErrorKey] = !isValid; + toggleValidCss(isValid, validationErrorKey); + + parentForm.$setValidity(validationErrorKey, isValid, this); + }; + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setPristine + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Sets the control to its pristine state. + * + * This method can be called to remove the 'ng-dirty' class and set the control to its pristine + * state (ng-pristine class). + */ + this.$setPristine = function () { + this.$dirty = false; + this.$pristine = true; + $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); + }; + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setViewValue + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Read a value from view. + * + * This method should be called from within a DOM event handler. + * For example {@link ng.directive:input input} or + * {@link ng.directive:select select} directives call it. + * + * It internally calls all `$parsers` (including validators) and updates the `$modelValue` and the actual model path. + * Lastly it calls all registered change listeners. + * + * @param {string} value Value from the view. + */ + this.$setViewValue = function(value) { + this.$viewValue = value; + + // change to dirty + if (this.$pristine) { + this.$dirty = true; + this.$pristine = false; + $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); + parentForm.$setDirty(); + } + + forEach(this.$parsers, function(fn) { + value = fn(value); + }); + + if (this.$modelValue !== value) { + this.$modelValue = value; + ngModelSet($scope, value); + forEach(this.$viewChangeListeners, function(listener) { + try { + listener(); + } catch(e) { + $exceptionHandler(e); + } + }); + } + }; + + // model -> value + var ctrl = this; + + $scope.$watch(function ngModelWatch() { + var value = ngModelGet($scope); + + // if scope model value and ngModel value are out of sync + if (ctrl.$modelValue !== value) { + + var formatters = ctrl.$formatters, + idx = formatters.length; + + ctrl.$modelValue = value; + while(idx--) { + value = formatters[idx](value); + } + + if (ctrl.$viewValue !== value) { + ctrl.$viewValue = value; + ctrl.$render(); + } + } + }); +}]; + + +/** + * @ngdoc directive + * @name ng.directive:ngModel + * + * @element input + * + * @description + * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a + * property on the scope using {@link ng.directive:ngModel.NgModelController NgModelController}, + * which is created and exposed by this directive. + * + * `ngModel` is responsible for: + * + * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` + * require. + * - Providing validation behavior (i.e. required, number, email, url). + * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). + * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`). + * - Registering the control with its parent {@link ng.directive:form form}. + * + * Note: `ngModel` will try to bind to the property given by evaluating the expression on the + * current scope. If the property doesn't already exist on this scope, it will be created + * implicitly and added to the scope. + * + * For best practices on using `ngModel`, see: + * + * - {@link https://github.com/angular/angular.js/wiki/Understanding-Scopes} + * + * For basic examples, how to use `ngModel`, see: + * + * - {@link ng.directive:input input} + * - {@link ng.directive:input.text text} + * - {@link ng.directive:input.checkbox checkbox} + * - {@link ng.directive:input.radio radio} + * - {@link ng.directive:input.number number} + * - {@link ng.directive:input.email email} + * - {@link ng.directive:input.url url} + * - {@link ng.directive:select select} + * - {@link ng.directive:textarea textarea} + * + */ +var ngModelDirective = function() { + return { + require: ['ngModel', '^?form'], + controller: NgModelController, + link: function(scope, element, attr, ctrls) { + // notify others, especially parent forms + + var modelCtrl = ctrls[0], + formCtrl = ctrls[1] || nullFormCtrl; + + formCtrl.$addControl(modelCtrl); + + scope.$on('$destroy', function() { + formCtrl.$removeControl(modelCtrl); + }); + } + }; +}; + + +/** + * @ngdoc directive + * @name ng.directive:ngChange + * + * @description + * Evaluate given expression when user changes the input. + * The expression is not evaluated when the value change is coming from the model. + * + * Note, this directive requires `ngModel` to be present. + * + * @element input + * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change + * in input value. + * + * @example + * + * + * + *
      + * + * + *
      + * debug = {{confirmed}}
      + * counter = {{counter}} + *
      + *
      + * + * it('should evaluate the expression if changing from view', function() { + * expect(binding('counter')).toEqual('0'); + * element('#ng-change-example1').click(); + * expect(binding('counter')).toEqual('1'); + * expect(binding('confirmed')).toEqual('true'); + * }); + * + * it('should not evaluate the expression if changing from model', function() { + * element('#ng-change-example2').click(); + * expect(binding('counter')).toEqual('0'); + * expect(binding('confirmed')).toEqual('true'); + * }); + * + *
      + */ +var ngChangeDirective = valueFn({ + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + ctrl.$viewChangeListeners.push(function() { + scope.$eval(attr.ngChange); + }); + } +}); + + +var requiredDirective = function() { + return { + require: '?ngModel', + link: function(scope, elm, attr, ctrl) { + if (!ctrl) return; + attr.required = true; // force truthy in case we are on non input element + + var validator = function(value) { + if (attr.required && ctrl.$isEmpty(value)) { + ctrl.$setValidity('required', false); + return; + } else { + ctrl.$setValidity('required', true); + return value; + } + }; + + ctrl.$formatters.push(validator); + ctrl.$parsers.unshift(validator); + + attr.$observe('required', function() { + validator(ctrl.$viewValue); + }); + } + }; +}; + + +/** + * @ngdoc directive + * @name ng.directive:ngList + * + * @description + * Text input that converts between a delimited string and an array of strings. The delimiter + * can be a fixed string (by default a comma) or a regular expression. + * + * @element input + * @param {string=} ngList optional delimiter that should be used to split the value. If + * specified in form `/something/` then the value will be converted into a regular expression. + * + * @example + + + +
      + List: + + Required! +
      + names = {{names}}
      + myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
      + myForm.namesInput.$error = {{myForm.namesInput.$error}}
      + myForm.$valid = {{myForm.$valid}}
      + myForm.$error.required = {{!!myForm.$error.required}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('names')).toEqual('["igor","misko","vojta"]'); + expect(binding('myForm.namesInput.$valid')).toEqual('true'); + expect(element('span.error').css('display')).toBe('none'); + }); + + it('should be invalid if empty', function() { + input('names').enter(''); + expect(binding('names')).toEqual(''); + expect(binding('myForm.namesInput.$valid')).toEqual('false'); + expect(element('span.error').css('display')).not().toBe('none'); + }); + +
      + */ +var ngListDirective = function() { + return { + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + var match = /\/(.*)\//.exec(attr.ngList), + separator = match && new RegExp(match[1]) || attr.ngList || ','; + + var parse = function(viewValue) { + // If the viewValue is invalid (say required but empty) it will be `undefined` + if (isUndefined(viewValue)) return; + + var list = []; + + if (viewValue) { + forEach(viewValue.split(separator), function(value) { + if (value) list.push(trim(value)); + }); + } + + return list; + }; + + ctrl.$parsers.push(parse); + ctrl.$formatters.push(function(value) { + if (isArray(value)) { + return value.join(', '); + } + + return undefined; + }); + + // Override the standard $isEmpty because an empty array means the input is empty. + ctrl.$isEmpty = function(value) { + return !value || !value.length; + }; + } + }; +}; + + +var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; +/** + * @ngdoc directive + * @name ng.directive:ngValue + * + * @description + * Binds the given expression to the value of `input[select]` or `input[radio]`, so + * that when the element is selected, the `ngModel` of that element is set to the + * bound value. + * + * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as + * shown below. + * + * @element input + * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute + * of the `input` element + * + * @example + + + +
      +

      Which is your favorite?

      + + +
      You chose {{my.favorite}}
      +
      +
      + + it('should initialize to model', function() { + expect(binding('my.favorite')).toEqual('unicorns'); + }); + it('should bind the values to the inputs', function() { + input('my.favorite').select('pizza'); + expect(binding('my.favorite')).toEqual('pizza'); + }); + +
      + */ +var ngValueDirective = function() { + return { + priority: 100, + compile: function(tpl, tplAttr) { + if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { + return function ngValueConstantLink(scope, elm, attr) { + attr.$set('value', scope.$eval(attr.ngValue)); + }; + } else { + return function ngValueLink(scope, elm, attr) { + scope.$watch(attr.ngValue, function valueWatchAction(value) { + attr.$set('value', value); + }); + }; + } + } + }; +}; + +/** + * @ngdoc directive + * @name ng.directive:ngBind + * @restrict AC + * + * @description + * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element + * with the value of a given expression, and to update the text content when the value of that + * expression changes. + * + * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like + * `{{ expression }}` which is similar but less verbose. + * + * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily + * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an + * element attribute, it makes the bindings invisible to the user while the page is loading. + * + * An alternative solution to this problem would be using the + * {@link ng.directive:ngCloak ngCloak} directive. + * + * + * @element ANY + * @param {expression} ngBind {@link guide/expression Expression} to evaluate. + * + * @example + * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. + + + +
      + Enter name:
      + Hello ! +
      +
      + + it('should check ng-bind', function() { + expect(using('.doc-example-live').binding('name')).toBe('Whirled'); + using('.doc-example-live').input('name').enter('world'); + expect(using('.doc-example-live').binding('name')).toBe('world'); + }); + +
      + */ +var ngBindDirective = ngDirective(function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBind); + scope.$watch(attr.ngBind, function ngBindWatchAction(value) { + // We are purposefully using == here rather than === because we want to + // catch when value is "null or undefined" + // jshint -W041 + element.text(value == undefined ? '' : value); + }); +}); + + +/** + * @ngdoc directive + * @name ng.directive:ngBindTemplate + * + * @description + * The `ngBindTemplate` directive specifies that the element + * text content should be replaced with the interpolation of the template + * in the `ngBindTemplate` attribute. + * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` + * expressions. This directive is needed since some HTML elements + * (such as TITLE and OPTION) cannot contain SPAN elements. + * + * @element ANY + * @param {string} ngBindTemplate template of form + * {{ expression }} to eval. + * + * @example + * Try it here: enter text in text box and watch the greeting change. + + + +
      + Salutation:
      + Name:
      +
      
      +       
      +
      + + it('should check ng-bind', function() { + expect(using('.doc-example-live').binding('salutation')). + toBe('Hello'); + expect(using('.doc-example-live').binding('name')). + toBe('World'); + using('.doc-example-live').input('salutation').enter('Greetings'); + using('.doc-example-live').input('name').enter('user'); + expect(using('.doc-example-live').binding('salutation')). + toBe('Greetings'); + expect(using('.doc-example-live').binding('name')). + toBe('user'); + }); + +
      + */ +var ngBindTemplateDirective = ['$interpolate', function($interpolate) { + return function(scope, element, attr) { + // TODO: move this to scenario runner + var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); + element.addClass('ng-binding').data('$binding', interpolateFn); + attr.$observe('ngBindTemplate', function(value) { + element.text(value); + }); + }; +}]; + + +/** + * @ngdoc directive + * @name ng.directive:ngBindHtml + * + * @description + * Creates a binding that will innerHTML the result of evaluating the `expression` into the current + * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link + * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` + * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in + * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to + * an explicitly trusted value via {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}. See the example + * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. + * + * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you + * will have an exception (instead of an exploit.) + * + * @element ANY + * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. + * + * @example + * Try it here: enter text in text box and watch the greeting change. + + + +
      +

      +
      +
      + + it('should check ng-bind-html', function() { + expect(using('.doc-example-live').binding('myHTML')). + toBe('I am an HTMLstring with links! and other stuff'); + }); + +
      + */ +var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { + return function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBindHtml); + + var parsed = $parse(attr.ngBindHtml); + function getStringValue() { return (parsed(scope) || '').toString(); } + + scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { + element.html($sce.getTrustedHtml(parsed(scope)) || ''); + }); + }; +}]; + +function classDirective(name, selector) { + name = 'ngClass' + name; + return function() { + return { + restrict: 'AC', + link: function(scope, element, attr) { + var oldVal; + + scope.$watch(attr[name], ngClassWatchAction, true); + + attr.$observe('class', function(value) { + ngClassWatchAction(scope.$eval(attr[name])); + }); + + + if (name !== 'ngClass') { + scope.$watch('$index', function($index, old$index) { + // jshint bitwise: false + var mod = $index & 1; + if (mod !== old$index & 1) { + if (mod === selector) { + addClass(scope.$eval(attr[name])); + } else { + removeClass(scope.$eval(attr[name])); + } + } + }); + } + + + function ngClassWatchAction(newVal) { + if (selector === true || scope.$index % 2 === selector) { + if (oldVal && !equals(newVal,oldVal)) { + removeClass(oldVal); + } + addClass(newVal); + } + oldVal = copy(newVal); + } + + + function removeClass(classVal) { + attr.$removeClass(flattenClasses(classVal)); + } + + + function addClass(classVal) { + attr.$addClass(flattenClasses(classVal)); + } + + function flattenClasses(classVal) { + if(isArray(classVal)) { + return classVal.join(' '); + } else if (isObject(classVal)) { + var classes = [], i = 0; + forEach(classVal, function(v, k) { + if (v) { + classes.push(k); + } + }); + return classes.join(' '); + } + + return classVal; + } + } + }; + }; +} + +/** + * @ngdoc directive + * @name ng.directive:ngClass + * @restrict AC + * + * @description + * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding + * an expression that represents all classes to be added. + * + * The directive won't add duplicate classes if a particular class was already set. + * + * When the expression changes, the previously added classes are removed and only then the + * new classes are added. + * + * @animations + * add - happens just before the class is applied to the element + * remove - happens just before the class is removed from the element + * + * @element ANY + * @param {expression} ngClass {@link guide/expression Expression} to eval. The result + * of the evaluation can be a string representing space delimited class + * names, an array, or a map of class names to boolean values. In the case of a map, the + * names of the properties whose values are truthy will be added as css classes to the + * element. + * + * @example Example that demonstrates basic bindings via ngClass directive. + + +

      Map Syntax Example

      + bold + strike + red +
      +

      Using String Syntax

      + +
      +

      Using Array Syntax

      +
      +
      +
      +
      + + .strike { + text-decoration: line-through; + } + .bold { + font-weight: bold; + } + .red { + color: red; + } + + + it('should let you toggle the class', function() { + + expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/bold/); + expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/red/); + + input('bold').check(); + expect(element('.doc-example-live p:first').prop('className')).toMatch(/bold/); + + input('red').check(); + expect(element('.doc-example-live p:first').prop('className')).toMatch(/red/); + }); + + it('should let you toggle string example', function() { + expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe(''); + input('style').enter('red'); + expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('red'); + }); + + it('array example should have 3 classes', function() { + expect(element('.doc-example-live p:last').prop('className')).toBe(''); + input('style1').enter('bold'); + input('style2').enter('strike'); + input('style3').enter('red'); + expect(element('.doc-example-live p:last').prop('className')).toBe('bold strike red'); + }); + +
      + + ## Animations + + The example below demonstrates how to perform animations using ngClass. + + + + + +
      + Sample Text +
      + + .base-class { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + } + + .base-class.my-class { + color: red; + font-size:3em; + } + + + it('should check ng-class', function() { + expect(element('.doc-example-live span').prop('className')).not(). + toMatch(/my-class/); + + using('.doc-example-live').element(':button:first').click(); + + expect(element('.doc-example-live span').prop('className')). + toMatch(/my-class/); + + using('.doc-example-live').element(':button:last').click(); + + expect(element('.doc-example-live span').prop('className')).not(). + toMatch(/my-class/); + }); + +
      + + + ## ngClass and pre-existing CSS3 Transitions/Animations + The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. + Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder + any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure + to view the step by step details of {@link ngAnimate.$animate#methods_addclass $animate.addClass} and + {@link ngAnimate.$animate#methods_removeclass $animate.removeClass}. + */ +var ngClassDirective = classDirective('', true); + +/** + * @ngdoc directive + * @name ng.directive:ngClassOdd + * @restrict AC + * + * @description + * The `ngClassOdd` and `ngClassEven` directives work exactly as + * {@link ng.directive:ngClass ngClass}, except they work in + * conjunction with `ngRepeat` and take effect only on odd (even) rows. + * + * This directive can be applied only within the scope of an + * {@link ng.directive:ngRepeat ngRepeat}. + * + * @element ANY + * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result + * of the evaluation can be a string representing space delimited class names or an array. + * + * @example + + +
        +
      1. + + {{name}} + +
      2. +
      +
      + + .odd { + color: red; + } + .even { + color: blue; + } + + + it('should check ng-class-odd and ng-class-even', function() { + expect(element('.doc-example-live li:first span').prop('className')). + toMatch(/odd/); + expect(element('.doc-example-live li:last span').prop('className')). + toMatch(/even/); + }); + +
      + */ +var ngClassOddDirective = classDirective('Odd', 0); + +/** + * @ngdoc directive + * @name ng.directive:ngClassEven + * @restrict AC + * + * @description + * The `ngClassOdd` and `ngClassEven` directives work exactly as + * {@link ng.directive:ngClass ngClass}, except they work in + * conjunction with `ngRepeat` and take effect only on odd (even) rows. + * + * This directive can be applied only within the scope of an + * {@link ng.directive:ngRepeat ngRepeat}. + * + * @element ANY + * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The + * result of the evaluation can be a string representing space delimited class names or an array. + * + * @example + + +
        +
      1. + + {{name}}       + +
      2. +
      +
      + + .odd { + color: red; + } + .even { + color: blue; + } + + + it('should check ng-class-odd and ng-class-even', function() { + expect(element('.doc-example-live li:first span').prop('className')). + toMatch(/odd/); + expect(element('.doc-example-live li:last span').prop('className')). + toMatch(/even/); + }); + +
      + */ +var ngClassEvenDirective = classDirective('Even', 1); + +/** + * @ngdoc directive + * @name ng.directive:ngCloak + * @restrict AC + * + * @description + * The `ngCloak` directive is used to prevent the Angular html template from being briefly + * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this + * directive to avoid the undesirable flicker effect caused by the html template display. + * + * The directive can be applied to the `` element, but the preferred usage is to apply + * multiple `ngCloak` directives to small portions of the page to permit progressive rendering + * of the browser view. + * + * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and + * `angular.min.js`. + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + *
      + * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
      + *   display: none !important;
      + * }
      + * 
      + * + * When this css rule is loaded by the browser, all html elements (including their children) that + * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive + * during the compilation of the template it deletes the `ngCloak` element attribute, making + * the compiled element visible. + * + * For the best result, the `angular.js` script must be loaded in the head section of the html + * document; alternatively, the css rule above must be included in the external stylesheet of the + * application. + * + * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they + * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css + * class `ngCloak` in addition to the `ngCloak` directive as shown in the example below. + * + * @element ANY + * + * @example + + +
      {{ 'hello' }}
      +
      {{ 'hello IE7' }}
      +
      + + it('should remove the template directive and css class', function() { + expect(element('.doc-example-live #template1').attr('ng-cloak')). + not().toBeDefined(); + expect(element('.doc-example-live #template2').attr('ng-cloak')). + not().toBeDefined(); + }); + +
      + * + */ +var ngCloakDirective = ngDirective({ + compile: function(element, attr) { + attr.$set('ngCloak', undefined); + element.removeClass('ng-cloak'); + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngController + * + * @description + * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular + * supports the principles behind the Model-View-Controller design pattern. + * + * MVC components in angular: + * + * * Model — The Model is scope properties; scopes are attached to the DOM where scope properties + * are accessed through bindings. + * * View — The template (HTML with data bindings) that is rendered into the View. + * * Controller — The `ngController` directive specifies a Controller class; the class contains business + * logic behind the application to decorate the scope with functions and values + * + * Note that you can also attach controllers to the DOM by declaring it in a route definition + * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller + * again using `ng-controller` in the template itself. This will cause the controller to be attached + * and executed twice. + * + * @element ANY + * @scope + * @param {expression} ngController Name of a globally accessible constructor function or an + * {@link guide/expression expression} that on the current scope evaluates to a + * constructor function. The controller instance can be published into a scope property + * by specifying `as propertyName`. + * + * @example + * Here is a simple form for editing user contact information. Adding, removing, clearing, and + * greeting are methods declared on the controller (see source tab). These methods can + * easily be called from the angular markup. Notice that the scope becomes the `this` for the + * controller's instance. This allows for easy access to the view data from the controller. Also + * notice that any changes to the data are automatically reflected in the View without the need + * for a manual update. The example is shown in two different declaration styles you may use + * according to preference. + + + +
      + Name: + [ greet ]
      + Contact: +
        +
      • + + + [ clear + | X ] +
      • +
      • [ add ]
      • +
      +
      +
      + + it('should check controller as', function() { + expect(element('#ctrl-as-exmpl>:input').val()).toBe('John Smith'); + expect(element('#ctrl-as-exmpl li:nth-child(1) input').val()) + .toBe('408 555 1212'); + expect(element('#ctrl-as-exmpl li:nth-child(2) input').val()) + .toBe('john.smith@example.org'); + + element('#ctrl-as-exmpl li:first a:contains("clear")').click(); + expect(element('#ctrl-as-exmpl li:first input').val()).toBe(''); + + element('#ctrl-as-exmpl li:last a:contains("add")').click(); + expect(element('#ctrl-as-exmpl li:nth-child(3) input').val()) + .toBe('yourname@example.org'); + }); + +
      + + + +
      + Name: + [ greet ]
      + Contact: +
        +
      • + + + [ clear + | X ] +
      • +
      • [ add ]
      • +
      +
      +
      + + it('should check controller', function() { + expect(element('#ctrl-exmpl>:input').val()).toBe('John Smith'); + expect(element('#ctrl-exmpl li:nth-child(1) input').val()) + .toBe('408 555 1212'); + expect(element('#ctrl-exmpl li:nth-child(2) input').val()) + .toBe('john.smith@example.org'); + + element('#ctrl-exmpl li:first a:contains("clear")').click(); + expect(element('#ctrl-exmpl li:first input').val()).toBe(''); + + element('#ctrl-exmpl li:last a:contains("add")').click(); + expect(element('#ctrl-exmpl li:nth-child(3) input').val()) + .toBe('yourname@example.org'); + }); + +
      + + */ +var ngControllerDirective = [function() { + return { + scope: true, + controller: '@' + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngCsp + * + * @element html + * @description + * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. + * + * This is necessary when developing things like Google Chrome Extensions. + * + * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). + * For us to be compatible, we just need to implement the "getterFn" in $parse without violating + * any of these restrictions. + * + * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` + * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will + * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will + * be raised. + * + * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically + * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). + * To make those directives work in CSP mode, include the `angular-csp.css` manually. + * + * In order to use this feature put the `ngCsp` directive on the root element of the application. + * + * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* + * + * @example + * This example shows how to apply the `ngCsp` directive to the `html` tag. +
      +     
      +     
      +     ...
      +     ...
      +     
      +   
      + */ + +// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap +// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute +// anywhere in the current doc + +/** + * @ngdoc directive + * @name ng.directive:ngClick + * + * @description + * The ngClick directive allows you to specify custom behavior when + * an element is clicked. + * + * @element ANY + * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon + * click. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + it('should check ng-click', function() { + expect(binding('count')).toBe('0'); + element('.doc-example-live :button').click(); + expect(binding('count')).toBe('1'); + }); + + + */ +/* + * A directive that allows creation of custom onclick handlers that are defined as angular + * expressions and are compiled and executed within the current scope. + * + * Events that are handled via these handler are always configured not to propagate further. + */ +var ngEventDirectives = {}; +forEach( + 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), + function(name) { + var directiveName = directiveNormalize('ng-' + name); + ngEventDirectives[directiveName] = ['$parse', function($parse) { + return { + compile: function($element, attr) { + var fn = $parse(attr[directiveName]); + return function(scope, element, attr) { + element.on(lowercase(name), function(event) { + scope.$apply(function() { + fn(scope, {$event:event}); + }); + }); + }; + } + }; + }]; + } +); + +/** + * @ngdoc directive + * @name ng.directive:ngDblclick + * + * @description + * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. + * + * @element ANY + * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon + * a dblclick. (The Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMousedown + * + * @description + * The ngMousedown directive allows you to specify custom behavior on mousedown event. + * + * @element ANY + * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon + * mousedown. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseup + * + * @description + * Specify custom behavior on mouseup event. + * + * @element ANY + * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon + * mouseup. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngMouseover + * + * @description + * Specify custom behavior on mouseover event. + * + * @element ANY + * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon + * mouseover. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseenter + * + * @description + * Specify custom behavior on mouseenter event. + * + * @element ANY + * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon + * mouseenter. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseleave + * + * @description + * Specify custom behavior on mouseleave event. + * + * @element ANY + * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon + * mouseleave. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMousemove + * + * @description + * Specify custom behavior on mousemove event. + * + * @element ANY + * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon + * mousemove. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeydown + * + * @description + * Specify custom behavior on keydown event. + * + * @element ANY + * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon + * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeyup + * + * @description + * Specify custom behavior on keyup event. + * + * @element ANY + * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon + * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeypress + * + * @description + * Specify custom behavior on keypress event. + * + * @element ANY + * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon + * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngSubmit + * + * @description + * Enables binding angular expressions to onsubmit events. + * + * Additionally it prevents the default action (which for form means sending the request to the + * server and reloading the current page) **but only if the form does not contain an `action` + * attribute**. + * + * @element form + * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`) + * + * @example + + + +
      + Enter text and hit enter: + + +
      list={{list}}
      +
      +
      + + it('should check ng-submit', function() { + expect(binding('list')).toBe('[]'); + element('.doc-example-live #submit').click(); + expect(binding('list')).toBe('["hello"]'); + expect(input('text').val()).toBe(''); + }); + it('should ignore empty strings', function() { + expect(binding('list')).toBe('[]'); + element('.doc-example-live #submit').click(); + element('.doc-example-live #submit').click(); + expect(binding('list')).toBe('["hello"]'); + }); + +
      + */ + +/** + * @ngdoc directive + * @name ng.directive:ngFocus + * + * @description + * Specify custom behavior on focus event. + * + * @element window, input, select, textarea, a + * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon + * focus. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngBlur + * + * @description + * Specify custom behavior on blur event. + * + * @element window, input, select, textarea, a + * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon + * blur. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngCopy + * + * @description + * Specify custom behavior on copy event. + * + * @element window, input, select, textarea, a + * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon + * copy. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngCut + * + * @description + * Specify custom behavior on cut event. + * + * @element window, input, select, textarea, a + * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon + * cut. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngPaste + * + * @description + * Specify custom behavior on paste event. + * + * @element window, input, select, textarea, a + * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon + * paste. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngIf + * @restrict A + * + * @description + * The `ngIf` directive removes or recreates a portion of the DOM tree based on an + * {expression}. If the expression assigned to `ngIf` evaluates to a false + * value then the element is removed from the DOM, otherwise a clone of the + * element is reinserted into the DOM. + * + * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the + * element in the DOM rather than changing its visibility via the `display` css property. A common + * case when this difference is significant is when using css selectors that rely on an element's + * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. + * + * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope + * is created when the element is restored. The scope created within `ngIf` inherits from + * its parent scope using + * {@link https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance prototypal inheritance}. + * An important implication of this is if `ngModel` is used within `ngIf` to bind to + * a javascript primitive defined in the parent scope. In this case any modifications made to the + * variable within the child scope will override (hide) the value in the parent scope. + * + * Also, `ngIf` recreates elements using their compiled state. An example of this behavior + * is if an element's class attribute is directly modified after it's compiled, using something like + * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element + * the added class will be lost because the original compiled state is used to regenerate the element. + * + * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` + * and `leave` effects. + * + * @animations + * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container + * leave - happens just before the ngIf contents are removed from the DOM + * + * @element ANY + * @scope + * @priority 600 + * @param {expression} ngIf If the {@link guide/expression expression} is falsy then + * the element is removed from the DOM tree. If it is truthy a copy of the compiled + * element is added to the DOM tree. + * + * @example + + + Click me:
      + Show when checked: + + I'm removed when the checkbox is unchecked. + +
      + + .animate-if { + background:white; + border:1px solid black; + padding:10px; + } + + /* + The transition styles can also be placed on the CSS base class above + */ + .animate-if.ng-enter, .animate-if.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + } + + .animate-if.ng-enter, + .animate-if.ng-leave.ng-leave-active { + opacity:0; + } + + .animate-if.ng-leave, + .animate-if.ng-enter.ng-enter-active { + opacity:1; + } + +
      + */ +var ngIfDirective = ['$animate', function($animate) { + return { + transclude: 'element', + priority: 600, + terminal: true, + restrict: 'A', + $$tlb: true, + compile: function (element, attr, transclude) { + return function ($scope, $element, $attr) { + var block, childScope; + $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { + + if (toBoolean(value)) { + + childScope = $scope.$new(); + transclude(childScope, function (clone) { + block = { + startNode: clone[0], + endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ') + }; + $animate.enter(clone, $element.parent(), $element); + }); + + } else { + + if (childScope) { + childScope.$destroy(); + childScope = null; + } + + if (block) { + $animate.leave(getBlockElements(block)); + block = null; + } + } + }); + }; + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngInclude + * @restrict ECA + * + * @description + * Fetches, compiles and includes an external HTML fragment. + * + * By default, the template URL is restricted to the same domain and protocol as the + * application document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols + * you may either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist them} or + * {@link ng.$sce#methods_trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link + * ng.$sce Strict Contextual Escaping}. + * + * In addition, the browser's + * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest + * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing + * (CORS)} policy may further restrict whether the template is successfully loaded. + * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` + * access on some browsers. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * + * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, + * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. + * @param {string=} onload Expression to evaluate when a new partial is loaded. + * + * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the content is loaded. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the expression evaluates to truthy value. + * + * @example + + +
      + + url of the template: {{template.url}} +
      +
      +
      +
      +
      +
      + + function Ctrl($scope) { + $scope.templates = + [ { name: 'template1.html', url: 'template1.html'} + , { name: 'template2.html', url: 'template2.html'} ]; + $scope.template = $scope.templates[0]; + } + + + Content of template1.html + + + Content of template2.html + + + .slide-animate-container { + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .slide-animate { + padding:10px; + } + + .slide-animate.ng-enter, .slide-animate.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + display:block; + padding:10px; + } + + .slide-animate.ng-enter { + top:-50px; + } + .slide-animate.ng-enter.ng-enter-active { + top:0; + } + + .slide-animate.ng-leave { + top:0; + } + .slide-animate.ng-leave.ng-leave-active { + top:50px; + } + + + it('should load template1.html', function() { + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template1.html/); + }); + it('should load template2.html', function() { + select('template').option('1'); + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template2.html/); + }); + it('should change to blank', function() { + select('template').option(''); + expect(element('.doc-example-live [ng-include]')).toBe(undefined); + }); + +
      + */ + + +/** + * @ngdoc event + * @name ng.directive:ngInclude#$includeContentRequested + * @eventOf ng.directive:ngInclude + * @eventType emit on the scope ngInclude was declared in + * @description + * Emitted every time the ngInclude content is requested. + */ + + +/** + * @ngdoc event + * @name ng.directive:ngInclude#$includeContentLoaded + * @eventOf ng.directive:ngInclude + * @eventType emit on the current ngInclude scope + * @description + * Emitted every time the ngInclude content is reloaded. + */ +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animate', '$sce', + function($http, $templateCache, $anchorScroll, $compile, $animate, $sce) { + return { + restrict: 'ECA', + priority: 400, + terminal: true, + transclude: 'element', + compile: function(element, attr, transclusion) { + var srcExp = attr.ngInclude || attr.src, + onloadExp = attr.onload || '', + autoScrollExp = attr.autoscroll; + + return function(scope, $element) { + var changeCounter = 0, + currentScope, + currentElement; + + var cleanupLastIncludeContent = function() { + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if(currentElement) { + $animate.leave(currentElement); + currentElement = null; + } + }; + + scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { + var afterAnimation = function() { + if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }; + var thisChangeId = ++changeCounter; + + if (src) { + $http.get(src, {cache: $templateCache}).success(function(response) { + if (thisChangeId !== changeCounter) return; + var newScope = scope.$new(); + + transclusion(newScope, function(clone) { + cleanupLastIncludeContent(); + + currentScope = newScope; + currentElement = clone; + + currentElement.html(response); + $animate.enter(currentElement, null, $element, afterAnimation); + $compile(currentElement.contents())(currentScope); + currentScope.$emit('$includeContentLoaded'); + scope.$eval(onloadExp); + }); + }).error(function() { + if (thisChangeId === changeCounter) cleanupLastIncludeContent(); + }); + scope.$emit('$includeContentRequested'); + } else { + cleanupLastIncludeContent(); + } + }); + }; + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngInit + * @restrict AC + * + * @description + * The `ngInit` directive allows you to evaluate an expression in the + * current scope. + * + *
      + * The only appropriate use of `ngInit` for aliasing special properties of + * {@link api/ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you + * should use {@link guide/controller controllers} rather than `ngInit` + * to initialize values on a scope. + *
      + * + * @element ANY + * @param {expression} ngInit {@link guide/expression Expression} to eval. + * + * @example + + + +
      +
      +
      + list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}}; +
      +
      +
      +
      + + it('should alias index positions', function() { + expect(element('.example-init').text()) + .toBe('list[ 0 ][ 0 ] = a;' + + 'list[ 0 ][ 1 ] = b;' + + 'list[ 1 ][ 0 ] = c;' + + 'list[ 1 ][ 1 ] = d;'); + }); + +
      + */ +var ngInitDirective = ngDirective({ + compile: function() { + return { + pre: function(scope, element, attrs) { + scope.$eval(attrs.ngInit); + } + }; + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngNonBindable + * @restrict AC + * @priority 1000 + * + * @description + * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current + * DOM element. This is useful if the element contains what appears to be Angular directives and + * bindings but which should be ignored by Angular. This could be the case if you have a site that + * displays snippets of code, for instance. + * + * @element ANY + * + * @example + * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, + * but the one wrapped in `ngNonBindable` is left alone. + * + * @example + + +
      Normal: {{1 + 2}}
      +
      Ignored: {{1 + 2}}
      +
      + + it('should check ng-non-bindable', function() { + expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); + expect(using('.doc-example-live').element('div:last').text()). + toMatch(/1 \+ 2/); + }); + +
      + */ +var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); + +/** + * @ngdoc directive + * @name ng.directive:ngPluralize + * @restrict EA + * + * @description + * # Overview + * `ngPluralize` is a directive that displays messages according to en-US localization rules. + * These rules are bundled with angular.js, but can be overridden + * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive + * by specifying the mappings between + * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * plural categories} and the strings to be displayed. + * + * # Plural categories and explicit number rules + * There are two + * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * plural categories} in Angular's default en-US locale: "one" and "other". + * + * While a plural category may match many numbers (for example, in en-US locale, "other" can match + * any number that is not 1), an explicit number rule can only match one number. For example, the + * explicit number rule for "3" matches the number 3. There are examples of plural categories + * and explicit number rules throughout the rest of this documentation. + * + * # Configuring ngPluralize + * You configure ngPluralize by providing 2 attributes: `count` and `when`. + * You can also provide an optional attribute, `offset`. + * + * The value of the `count` attribute can be either a string or an {@link guide/expression + * Angular expression}; these are evaluated on the current scope for its bound value. + * + * The `when` attribute specifies the mappings between plural categories and the actual + * string to be displayed. The value of the attribute should be a JSON object. + * + * The following example shows how to configure ngPluralize: + * + *
      + * 
      + * 
      + *
      + * + * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not + * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" + * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for + * other numbers, for example 12, so that instead of showing "12 people are viewing", you can + * show "a dozen people are viewing". + * + * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted + * into pluralized strings. In the previous example, Angular will replace `{}` with + * `{{personCount}}`. The closed braces `{}` is a placeholder + * for {{numberExpression}}. + * + * # Configuring ngPluralize with offset + * The `offset` attribute allows further customization of pluralized text, which can result in + * a better user experience. For example, instead of the message "4 people are viewing this document", + * you might display "John, Kate and 2 others are viewing this document". + * The offset attribute allows you to offset a number by any desired value. + * Let's take a look at an example: + * + *
      + * 
      + * 
      + * 
      + * + * Notice that we are still using two plural categories(one, other), but we added + * three explicit number rules 0, 1 and 2. + * When one person, perhaps John, views the document, "John is viewing" will be shown. + * When three people view the document, no explicit number rule is found, so + * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. + * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" + * is shown. + * + * Note that when you specify offsets, you must provide explicit number rules for + * numbers from 0 up to and including the offset. If you use an offset of 3, for example, + * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for + * plural categories "one" and "other". + * + * @param {string|expression} count The variable to be bounded to. + * @param {string} when The mapping between plural category to its corresponding strings. + * @param {number=} offset Offset to deduct from the total number. + * + * @example + + + +
      + Person 1:
      + Person 2:
      + Number of People:
      + + + Without Offset: + +
      + + + With Offset(2): + + +
      +
      + + it('should show correct pluralized string', function() { + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('1 person is viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor is viewing.'); + + using('.doc-example-live').input('personCount').enter('0'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('Nobody is viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Nobody is viewing.'); + + using('.doc-example-live').input('personCount').enter('2'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('2 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor and Misko are viewing.'); + + using('.doc-example-live').input('personCount').enter('3'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('3 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and one other person are viewing.'); + + using('.doc-example-live').input('personCount').enter('4'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('4 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and 2 other people are viewing.'); + }); + + it('should show data-binded names', function() { + using('.doc-example-live').input('personCount').enter('4'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and 2 other people are viewing.'); + + using('.doc-example-live').input('person1').enter('Di'); + using('.doc-example-live').input('person2').enter('Vojta'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Di, Vojta and 2 other people are viewing.'); + }); + +
      + */ +var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { + var BRACE = /{}/g; + return { + restrict: 'EA', + link: function(scope, element, attr) { + var numberExp = attr.count, + whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs + offset = attr.offset || 0, + whens = scope.$eval(whenExp) || {}, + whensExpFns = {}, + startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + isWhen = /^when(Minus)?(.+)$/; + + forEach(attr, function(expression, attributeName) { + if (isWhen.test(attributeName)) { + whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = + element.attr(attr.$attr[attributeName]); + } + }); + forEach(whens, function(expression, key) { + whensExpFns[key] = + $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + + offset + endSymbol)); + }); + + scope.$watch(function ngPluralizeWatch() { + var value = parseFloat(scope.$eval(numberExp)); + + if (!isNaN(value)) { + //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, + //check it against pluralization rules in $locale service + if (!(value in whens)) value = $locale.pluralCat(value - offset); + return whensExpFns[value](scope, element, true); + } else { + return ''; + } + }, function ngPluralizeWatchAction(newVal) { + element.text(newVal); + }); + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngRepeat + * + * @description + * The `ngRepeat` directive instantiates a template once per item from a collection. Each template + * instance gets its own scope, where the given loop variable is set to the current collection item, + * and `$index` is set to the item index or key. + * + * Special properties are exposed on the local scope of each template instance, including: + * + * | Variable | Type | Details | + * |-----------|-----------------|-----------------------------------------------------------------------------| + * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | + * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | + * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | + * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | + * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | + * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | + * + * + * # Special repeat start and end points + * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending + * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. + * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) + * up to and including the ending HTML tag where **ng-repeat-end** is placed. + * + * The example below makes use of this feature: + *
      + *   
      + * Header {{ item }} + *
      + *
      + * Body {{ item }} + *
      + *
      + * Footer {{ item }} + *
      + *
      + * + * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: + *
      + *   
      + * Header A + *
      + *
      + * Body A + *
      + *
      + * Footer A + *
      + *
      + * Header B + *
      + *
      + * Body B + *
      + *
      + * Footer B + *
      + *
      + * + * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such + * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). + * + * @animations + * enter - when a new item is added to the list or when an item is revealed after a filter + * leave - when an item is removed from the list or when an item is filtered out + * move - when an adjacent item is filtered out causing a reorder or when the item contents are reordered + * + * @element ANY + * @scope + * @priority 1000 + * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These + * formats are currently supported: + * + * * `variable in expression` – where variable is the user defined loop variable and `expression` + * is a scope expression giving the collection to enumerate. + * + * For example: `album in artist.albums`. + * + * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, + * and `expression` is the scope expression giving the collection to enumerate. + * + * For example: `(name, age) in {'adam':10, 'amalie':12}`. + * + * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function + * which can be used to associate the objects in the collection with the DOM elements. If no tracking function + * is specified the ng-repeat associates elements by identity in the collection. It is an error to have + * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are + * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, + * before specifying a tracking expression. + * + * For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements + * will be associated by item identity in the array. + * + * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique + * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements + * with the corresponding item in the array by identity. Moving the same object in array would move the DOM + * element in the same way ian the DOM. + * + * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this + * case the object identity does not matter. Two objects are considered equivalent as long as their `id` + * property is same. + * + * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter + * to items in conjunction with a tracking expression. + * + * @example + * This example initializes the scope to a list of names and + * then uses `ngRepeat` to display every person: + + +
      + I have {{friends.length}} friends. They are: + +
        +
      • + [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. +
      • +
      +
      +
      + + .example-animate-container { + background:white; + border:1px solid black; + list-style:none; + margin:0; + padding:0 10px; + } + + .animate-repeat { + line-height:40px; + list-style:none; + box-sizing:border-box; + } + + .animate-repeat.ng-move, + .animate-repeat.ng-enter, + .animate-repeat.ng-leave { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + } + + .animate-repeat.ng-leave.ng-leave-active, + .animate-repeat.ng-move, + .animate-repeat.ng-enter { + opacity:0; + max-height:0; + } + + .animate-repeat.ng-leave, + .animate-repeat.ng-move.ng-move-active, + .animate-repeat.ng-enter.ng-enter-active { + opacity:1; + max-height:40px; + } + + + it('should render initial data set', function() { + var r = using('.doc-example-live').repeater('ul li'); + expect(r.count()).toBe(10); + expect(r.row(0)).toEqual(["1","John","25"]); + expect(r.row(1)).toEqual(["2","Jessie","30"]); + expect(r.row(9)).toEqual(["10","Samantha","60"]); + expect(binding('friends.length')).toBe("10"); + }); + + it('should update repeater when filter predicate changes', function() { + var r = using('.doc-example-live').repeater('ul li'); + expect(r.count()).toBe(10); + + input('q').enter('ma'); + + expect(r.count()).toBe(2); + expect(r.row(0)).toEqual(["1","Mary","28"]); + expect(r.row(1)).toEqual(["2","Samantha","60"]); + }); + +
      + */ +var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { + var NG_REMOVED = '$$NG_REMOVED'; + var ngRepeatMinErr = minErr('ngRepeat'); + return { + transclude: 'element', + priority: 1000, + terminal: true, + $$tlb: true, + compile: function(element, attr, linker) { + return function($scope, $element, $attr){ + var expression = $attr.ngRepeat; + var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/), + trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, + lhs, rhs, valueIdentifier, keyIdentifier, + hashFnLocals = {$id: hashKey}; + + if (!match) { + throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", + expression); + } + + lhs = match[1]; + rhs = match[2]; + trackByExp = match[4]; + + if (trackByExp) { + trackByExpGetter = $parse(trackByExp); + trackByIdExpFn = function(key, value, index) { + // assign key, value, and $index to the locals so that they can be used in hash functions + if (keyIdentifier) hashFnLocals[keyIdentifier] = key; + hashFnLocals[valueIdentifier] = value; + hashFnLocals.$index = index; + return trackByExpGetter($scope, hashFnLocals); + }; + } else { + trackByIdArrayFn = function(key, value) { + return hashKey(value); + }; + trackByIdObjFn = function(key) { + return key; + }; + } + + match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); + if (!match) { + throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", + lhs); + } + valueIdentifier = match[3] || match[1]; + keyIdentifier = match[2]; + + // Store a list of elements from previous run. This is a hash where key is the item from the + // iterator, and the value is objects with following properties. + // - scope: bound scope + // - element: previous element. + // - index: position + var lastBlockMap = {}; + + //watch props + $scope.$watchCollection(rhs, function ngRepeatAction(collection){ + var index, length, + previousNode = $element[0], // current position of the node + nextNode, + // Same as lastBlockMap but it has the current state. It will become the + // lastBlockMap on the next iteration. + nextBlockMap = {}, + arrayLength, + childScope, + key, value, // key/value of iteration + trackById, + trackByIdFn, + collectionKeys, + block, // last object information {scope, element, id} + nextBlockOrder = [], + elementsToRemove; + + + if (isArrayLike(collection)) { + collectionKeys = collection; + trackByIdFn = trackByIdExpFn || trackByIdArrayFn; + } else { + trackByIdFn = trackByIdExpFn || trackByIdObjFn; + // if object, extract keys, sort them and use to determine order of iteration over obj props + collectionKeys = []; + for (key in collection) { + if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { + collectionKeys.push(key); + } + } + collectionKeys.sort(); + } + + arrayLength = collectionKeys.length; + + // locate existing items + length = nextBlockOrder.length = collectionKeys.length; + for(index = 0; index < length; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + trackById = trackByIdFn(key, value, index); + assertNotHasOwnProperty(trackById, '`track by` id'); + if(lastBlockMap.hasOwnProperty(trackById)) { + block = lastBlockMap[trackById]; + delete lastBlockMap[trackById]; + nextBlockMap[trackById] = block; + nextBlockOrder[index] = block; + } else if (nextBlockMap.hasOwnProperty(trackById)) { + // restore lastBlockMap + forEach(nextBlockOrder, function(block) { + if (block && block.startNode) lastBlockMap[block.id] = block; + }); + // This is a duplicate and we need to throw an error + throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", + expression, trackById); + } else { + // new never before seen block + nextBlockOrder[index] = { id: trackById }; + nextBlockMap[trackById] = false; + } + } + + // remove existing items + for (key in lastBlockMap) { + // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn + if (lastBlockMap.hasOwnProperty(key)) { + block = lastBlockMap[key]; + elementsToRemove = getBlockElements(block); + $animate.leave(elementsToRemove); + forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); + block.scope.$destroy(); + } + } + + // we are not using forEach for perf reasons (trying to avoid #call) + for (index = 0, length = collectionKeys.length; index < length; index++) { + key = (collection === collectionKeys) ? index : collectionKeys[index]; + value = collection[key]; + block = nextBlockOrder[index]; + if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode; + + if (block.startNode) { + // if we have already seen this object, then we need to reuse the + // associated scope/element + childScope = block.scope; + + nextNode = previousNode; + do { + nextNode = nextNode.nextSibling; + } while(nextNode && nextNode[NG_REMOVED]); + + if (block.startNode != nextNode) { + // existing item which got moved + $animate.move(getBlockElements(block), null, jqLite(previousNode)); + } + previousNode = block.endNode; + } else { + // new item which we don't know about + childScope = $scope.$new(); + } + + childScope[valueIdentifier] = value; + if (keyIdentifier) childScope[keyIdentifier] = key; + childScope.$index = index; + childScope.$first = (index === 0); + childScope.$last = (index === (arrayLength - 1)); + childScope.$middle = !(childScope.$first || childScope.$last); + // jshint bitwise: false + childScope.$odd = !(childScope.$even = (index&1) === 0); + // jshint bitwise: true + + if (!block.startNode) { + linker(childScope, function(clone) { + clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); + $animate.enter(clone, null, jqLite(previousNode)); + previousNode = clone; + block.scope = childScope; + block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0]; + block.endNode = clone[clone.length - 1]; + nextBlockMap[block.id] = block; + }); + } + } + lastBlockMap = nextBlockMap; + }); + }; + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngShow + * + * @description + * The `ngShow` directive shows or hides the given HTML element based on the expression + * provided to the ngShow attribute. The element is shown or hidden by removing or adding + * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined + * in AngularJS and sets the display style to none (using an !important flag). + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + *
      + * 
      + * 
      + * + * + *
      + *
      + * + * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute + * on the element causing it to become hidden. When true, the ng-hide CSS class is removed + * from the element causing the element not to appear hidden. + * + * ## Why is !important used? + * + * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector + * can be easily overridden by heavier selectors. For example, something as simple + * as changing the display style on a HTML list item would make hidden elements appear visible. + * This also becomes a bigger issue when dealing with CSS frameworks. + * + * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector + * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the + * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. + * + * ### Overriding .ng-hide + * + * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by + * restating the styles for the .ng-hide class in CSS: + *
      + * .ng-hide {
      + *   //!annotate CSS Specificity|Not to worry, this will override the AngularJS default...
      + *   display:block!important;
      + *
      + *   //this is just another form of hiding an element
      + *   position:absolute;
      + *   top:-9999px;
      + *   left:-9999px;
      + * }
      + * 
      + * + * Just remember to include the important flag so the CSS override will function. + * + * ## A note about animations with ngShow + * + * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression + * is true and false. This system works like the animation system present with ngClass except that + * you must also include the !important flag to override the display property + * so that you can perform an animation when the element is hidden during the time of the animation. + * + *
      + * //
      + * //a working example can be found at the bottom of this page
      + * //
      + * .my-element.ng-hide-add, .my-element.ng-hide-remove {
      + *   transition:0.5s linear all;
      + *   display:block!important;
      + * }
      + *
      + * .my-element.ng-hide-add { ... }
      + * .my-element.ng-hide-add.ng-hide-add-active { ... }
      + * .my-element.ng-hide-remove { ... }
      + * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
      + * 
      + * + * @animations + * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible + * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden + * + * @element ANY + * @param {expression} ngShow If the {@link guide/expression expression} is truthy + * then the element is shown or hidden respectively. + * + * @example + + + Click me:
      +
      + Show: +
      + I show up when your checkbox is checked. +
      +
      +
      + Hide: +
      + I hide when your checkbox is checked. +
      +
      +
      + + .animate-show { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + line-height:20px; + opacity:1; + padding:10px; + border:1px solid black; + background:white; + } + + .animate-show.ng-hide-add, + .animate-show.ng-hide-remove { + display:block!important; + } + + .animate-show.ng-hide { + line-height:0; + opacity:0; + padding:0 10px; + } + + .check-element { + padding:10px; + border:1px solid black; + background:white; + } + + + it('should check ng-show / ng-hide', function() { + expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); + expect(element('.doc-example-live span:last:visible').count()).toEqual(1); + + input('checked').check(); + + expect(element('.doc-example-live span:first:visible').count()).toEqual(1); + expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); + }); + +
      + */ +var ngShowDirective = ['$animate', function($animate) { + return function(scope, element, attr) { + scope.$watch(attr.ngShow, function ngShowWatchAction(value){ + $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); + }); + }; +}]; + + +/** + * @ngdoc directive + * @name ng.directive:ngHide + * + * @description + * The `ngHide` directive shows or hides the given HTML element based on the expression + * provided to the ngHide attribute. The element is shown or hidden by removing or adding + * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined + * in AngularJS and sets the display style to none (using an !important flag). + * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). + * + *
      + * 
      + * 
      + * + * + *
      + *
      + * + * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute + * on the element causing it to become hidden. When false, the ng-hide CSS class is removed + * from the element causing the element not to appear hidden. + * + * ## Why is !important used? + * + * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector + * can be easily overridden by heavier selectors. For example, something as simple + * as changing the display style on a HTML list item would make hidden elements appear visible. + * This also becomes a bigger issue when dealing with CSS frameworks. + * + * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector + * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the + * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. + * + * ### Overriding .ng-hide + * + * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by + * restating the styles for the .ng-hide class in CSS: + *
      + * .ng-hide {
      + *   //!annotate CSS Specificity|Not to worry, this will override the AngularJS default...
      + *   display:block!important;
      + *
      + *   //this is just another form of hiding an element
      + *   position:absolute;
      + *   top:-9999px;
      + *   left:-9999px;
      + * }
      + * 
      + * + * Just remember to include the important flag so the CSS override will function. + * + * ## A note about animations with ngHide + * + * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression + * is true and false. This system works like the animation system present with ngClass, except that + * you must also include the !important flag to override the display property so + * that you can perform an animation when the element is hidden during the time of the animation. + * + *
      + * //
      + * //a working example can be found at the bottom of this page
      + * //
      + * .my-element.ng-hide-add, .my-element.ng-hide-remove {
      + *   transition:0.5s linear all;
      + *   display:block!important;
      + * }
      + *
      + * .my-element.ng-hide-add { ... }
      + * .my-element.ng-hide-add.ng-hide-add-active { ... }
      + * .my-element.ng-hide-remove { ... }
      + * .my-element.ng-hide-remove.ng-hide-remove-active { ... }
      + * 
      + * + * @animations + * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden + * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible + * + * @element ANY + * @param {expression} ngHide If the {@link guide/expression expression} is truthy then + * the element is shown or hidden respectively. + * + * @example + + + Click me:
      +
      + Show: +
      + I show up when your checkbox is checked. +
      +
      +
      + Hide: +
      + I hide when your checkbox is checked. +
      +
      +
      + + .animate-hide { + -webkit-transition:all linear 0.5s; + transition:all linear 0.5s; + line-height:20px; + opacity:1; + padding:10px; + border:1px solid black; + background:white; + } + + .animate-hide.ng-hide-add, + .animate-hide.ng-hide-remove { + display:block!important; + } + + .animate-hide.ng-hide { + line-height:0; + opacity:0; + padding:0 10px; + } + + .check-element { + padding:10px; + border:1px solid black; + background:white; + } + + + it('should check ng-show / ng-hide', function() { + expect(element('.doc-example-live .check-element:first:hidden').count()).toEqual(1); + expect(element('.doc-example-live .check-element:last:visible').count()).toEqual(1); + + input('checked').check(); + + expect(element('.doc-example-live .check-element:first:visible').count()).toEqual(1); + expect(element('.doc-example-live .check-element:last:hidden').count()).toEqual(1); + }); + +
      + */ +var ngHideDirective = ['$animate', function($animate) { + return function(scope, element, attr) { + scope.$watch(attr.ngHide, function ngHideWatchAction(value){ + $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); + }); + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngStyle + * @restrict AC + * + * @description + * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. + * + * @element ANY + * @param {expression} ngStyle {@link guide/expression Expression} which evals to an + * object whose keys are CSS style names and values are corresponding values for those CSS + * keys. + * + * @example + + + + +
      + Sample Text +
      myStyle={{myStyle}}
      +
      + + span { + color: black; + } + + + it('should check ng-style', function() { + expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); + element('.doc-example-live :button[value=set]').click(); + expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)'); + element('.doc-example-live :button[value=clear]').click(); + expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); + }); + +
      + */ +var ngStyleDirective = ngDirective(function(scope, element, attr) { + scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { + if (oldStyles && (newStyles !== oldStyles)) { + forEach(oldStyles, function(val, style) { element.css(style, '');}); + } + if (newStyles) element.css(newStyles); + }, true); +}); + +/** + * @ngdoc directive + * @name ng.directive:ngSwitch + * @restrict EA + * + * @description + * The ngSwitch directive is used to conditionally swap DOM structure on your template based on a scope expression. + * Elements within ngSwitch but without ngSwitchWhen or ngSwitchDefault directives will be preserved at the location + * as specified in the template. + * + * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it + * from the template cache), ngSwitch simply choses one of the nested elements and makes it visible based on which element + * matches the value obtained from the evaluated expression. In other words, you define a container element + * (where you place the directive), place an expression on the **on="..." attribute** + * (or the **ng-switch="..." attribute**), define any inner elements inside of the directive and place + * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on + * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default + * attribute is displayed. + * + * @animations + * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container + * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM + * + * @usage + * + * ... + * ... + * ... + * + * + * @scope + * @priority 800 + * @param {*} ngSwitch|on expression to match against ng-switch-when. + * @paramDescription + * On child elements add: + * + * * `ngSwitchWhen`: the case statement to match against. If match then this + * case will be displayed. If the same match appears multiple times, all the + * elements will be displayed. + * * `ngSwitchDefault`: the default case when no other case match. If there + * are multiple default cases, all of them will be displayed when no other + * case match. + * + * + * @example + + +
      + + selection={{selection}} +
      +
      +
      Settings Div
      +
      Home Span
      +
      default
      +
      +
      +
      + + function Ctrl($scope) { + $scope.items = ['settings', 'home', 'other']; + $scope.selection = $scope.items[0]; + } + + + .animate-switch-container { + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .animate-switch { + padding:10px; + } + + .animate-switch.ng-animate { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + } + + .animate-switch.ng-leave.ng-leave-active, + .animate-switch.ng-enter { + top:-50px; + } + .animate-switch.ng-leave, + .animate-switch.ng-enter.ng-enter-active { + top:0; + } + + + it('should start in settings', function() { + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/); + }); + it('should change to home', function() { + select('selection').option('home'); + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/); + }); + it('should select default', function() { + select('selection').option('other'); + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/); + }); + +
      + */ +var ngSwitchDirective = ['$animate', function($animate) { + return { + restrict: 'EA', + require: 'ngSwitch', + + // asks for $scope to fool the BC controller module + controller: ['$scope', function ngSwitchController() { + this.cases = {}; + }], + link: function(scope, element, attr, ngSwitchController) { + var watchExpr = attr.ngSwitch || attr.on, + selectedTranscludes, + selectedElements, + selectedScopes = []; + + scope.$watch(watchExpr, function ngSwitchWatchAction(value) { + for (var i= 0, ii=selectedScopes.length; i + + +
      +
      +
      + {{text}} +
      +
      + + it('should have transcluded', function() { + input('title').enter('TITLE'); + input('text').enter('TEXT'); + expect(binding('title')).toEqual('TITLE'); + expect(binding('text')).toEqual('TEXT'); + }); + + + * + */ +var ngTranscludeDirective = ngDirective({ + controller: ['$element', '$transclude', function($element, $transclude) { + if (!$transclude) { + throw minErr('ngTransclude')('orphan', + 'Illegal use of ngTransclude directive in the template! ' + + 'No parent directive that requires a transclusion found. ' + + 'Element: {0}', + startingTag($element)); + } + + // remember the transclusion fn but call it during linking so that we don't process transclusion before directives on + // the parent element even when the transclusion replaces the current element. (we can't use priority here because + // that applies only to compile fns and not controllers + this.$transclude = $transclude; + }], + + link: function($scope, $element, $attrs, controller) { + controller.$transclude(function(clone) { + $element.html(''); + $element.append(clone); + }); + } +}); + +/** + * @ngdoc directive + * @name ng.directive:script + * @restrict E + * + * @description + * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the + * template can be used by `ngInclude`, `ngView` or directive templates. + * + * @param {'text/ng-template'} type must be set to `'text/ng-template'` + * + * @example + + + + + Load inlined template +
      +
      + + it('should load template defined inside script tag', function() { + element('#tpl-link').click(); + expect(element('#tpl-content').text()).toMatch(/Content of the template/); + }); + +
      + */ +var scriptDirective = ['$templateCache', function($templateCache) { + return { + restrict: 'E', + terminal: true, + compile: function(element, attr) { + if (attr.type == 'text/ng-template') { + var templateUrl = attr.id, + // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent + text = element[0].text; + + $templateCache.put(templateUrl, text); + } + } + }; +}]; + +var ngOptionsMinErr = minErr('ngOptions'); +/** + * @ngdoc directive + * @name ng.directive:select + * @restrict E + * + * @description + * HTML `SELECT` element with angular data-binding. + * + * # `ngOptions` + * + * The `ngOptions` attribute can be used to dynamically generate a list of `` + * DOM element. + * * `trackexpr`: Used when working with an array of objects. The result of this expression will be + * used to identify the objects in the array. The `trackexpr` will most likely refer to the + * `value` variable (e.g. `value.propertyName`). + * + * @example + + + +
      +
        +
      • + Name: + [X] +
      • +
      • + [add] +
      • +
      +
      + Color (null not allowed): +
      + + Color (null allowed): + + +
      + + Color grouped by shade: +
      + + + Select bogus.
      +
      + Currently selected: {{ {selected_color:color} }} +
      +
      +
      +
      + + it('should check ng-options', function() { + expect(binding('{selected_color:color}')).toMatch('red'); + select('color').option('0'); + expect(binding('{selected_color:color}')).toMatch('black'); + using('.nullable').select('color').option(''); + expect(binding('{selected_color:color}')).toMatch('null'); + }); + +
      + */ + +var ngOptionsDirective = valueFn({ terminal: true }); +// jshint maxlen: false +var selectDirective = ['$compile', '$parse', function($compile, $parse) { + //0000111110000000000022220000000000000000000000333300000000000000444444444444444000000000555555555555555000000066666666666666600000000000000007777000000000000000000088888 + var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/, + nullModelCtrl = {$setViewValue: noop}; +// jshint maxlen: 100 + + return { + restrict: 'E', + require: ['select', '?ngModel'], + controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { + var self = this, + optionsMap = {}, + ngModelCtrl = nullModelCtrl, + nullOption, + unknownOption; + + + self.databound = $attrs.ngModel; + + + self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { + ngModelCtrl = ngModelCtrl_; + nullOption = nullOption_; + unknownOption = unknownOption_; + }; + + + self.addOption = function(value) { + assertNotHasOwnProperty(value, '"option value"'); + optionsMap[value] = true; + + if (ngModelCtrl.$viewValue == value) { + $element.val(value); + if (unknownOption.parent()) unknownOption.remove(); + } + }; + + + self.removeOption = function(value) { + if (this.hasOption(value)) { + delete optionsMap[value]; + if (ngModelCtrl.$viewValue == value) { + this.renderUnknownOption(value); + } + } + }; + + + self.renderUnknownOption = function(val) { + var unknownVal = '? ' + hashKey(val) + ' ?'; + unknownOption.val(unknownVal); + $element.prepend(unknownOption); + $element.val(unknownVal); + unknownOption.prop('selected', true); // needed for IE + }; + + + self.hasOption = function(value) { + return optionsMap.hasOwnProperty(value); + }; + + $scope.$on('$destroy', function() { + // disable unknown option so that we don't do work when the whole select is being destroyed + self.renderUnknownOption = noop; + }); + }], + + link: function(scope, element, attr, ctrls) { + // if ngModel is not defined, we don't need to do anything + if (!ctrls[1]) return; + + var selectCtrl = ctrls[0], + ngModelCtrl = ctrls[1], + multiple = attr.multiple, + optionsExp = attr.ngOptions, + nullOption = false, // if false, user will not be able to select it (used by ngOptions) + emptyOption, + // we can't just jqLite('"; + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Support: Opera 10-12/IE8 + // ^= $= *= and empty values + // Should not select anything + // Support: Windows 8 Native Apps + // The type attribute is restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "t", "" ); + + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); + + if ( compare ) { + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } + + // Not directly comparable, sort on existence of method + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val === undefined ? + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null : + val; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] && match[4] !== undefined ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + } + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) + ); + return results; +} + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + elem[ name ] === true ? name.toLowerCase() : null; + } + }); +} + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function( support ) { + + var all, a, input, select, fragment, opt, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
      a"; + + // Finish early in limited (non-browser) environments + all = div.getElementsByTagName("*") || []; + a = div.getElementsByTagName("a")[ 0 ]; + if ( !a || !a.style || !all.length ) { + return support; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + support.getSetAttribute = div.className !== "t"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName("tbody").length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName("link").length; + + // Get the style information from getAttribute + // (IE uses .cssText instead) + support.style = /top/.test( a.getAttribute("style") ); + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + support.hrefNormalized = a.getAttribute("href") === "/a"; + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + support.opacity = /^0.5/.test( a.style.opacity ); + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + support.cssFloat = !!a.style.cssFloat; + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + support.checkOn = !!input.value; + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + support.optSelected = opt.selected; + + // Tests for enctype support on a form (#6743) + support.enctype = !!document.createElement("form").enctype; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>"; + + // Will be defined later + support.inlineBlockNeedsLayout = false; + support.shrinkWrapBlocks = false; + support.pixelPosition = false; + support.deleteExpando = true; + support.noCloneEvent = true; + support.reliableMarginRight = true; + support.boxSizingReliable = true; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Support: IE<9 + // Iteration over object's inherited properties before its own. + for ( i in jQuery( support ) ) { + break; + } + support.ownLast = i !== "0"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "
      t
      "; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior. + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + + // Workaround failing boxSizing test due to offsetWidth returning wrong value + // with some non-1 values of body zoom, ticket #13543 + jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() { + support.boxSizing = div.offsetWidth === 4; + }); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "
      "; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +})({}); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "applet": true, + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + data = null, + i = 0, + elem = this[0]; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( name.indexOf("data-") === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return arguments.length > 1 ? + + // Sets one value + this.each(function() { + jQuery.data( this, key, value ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n\f]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value; + + if ( typeof stateVal === "boolean" && type === "string" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // Use proper attribute retrieval(#6932, #12072) + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) { + optionSet = true; + } + } + + // force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( jQuery.expr.match.bool.test( name ) ) { + // Set corresponding property to false + if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + elem[ propName ] = false; + // Support: IE<9 + // Also clear defaultChecked/defaultSelected (if appropriate) + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? + ret : + ( elem[ name ] = value ); + + } else { + return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? + ret : + elem[ name ]; + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + return tabindex ? + parseInt( tabindex, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + -1; + } + } + } +}); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr; + + jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ? + function( elem, name, isXML ) { + var fn = jQuery.expr.attrHandle[ name ], + ret = isXML ? + undefined : + /* jshint eqeqeq: false */ + (jQuery.expr.attrHandle[ name ] = undefined) != + getter( elem, name, isXML ) ? + + name.toLowerCase() : + null; + jQuery.expr.attrHandle[ name ] = fn; + return ret; + } : + function( elem, name, isXML ) { + return isXML ? + undefined : + elem[ jQuery.camelCase( "default-" + name ) ] ? + name.toLowerCase() : + null; + }; +}); + +// fix oldIE attroperties +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = { + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords = + // Some attributes are constructed with empty-string values when not defined + function( elem, name, isXML ) { + var ret; + return isXML ? + undefined : + (ret = elem.getAttributeNode( name )) && ret.value !== "" ? + ret.value : + null; + }; + jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ret.specified ? + ret.value : + undefined; + }, + set: nodeHook.set + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }; + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }; +} + +jQuery.each([ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +}); + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }; + if ( !jQuery.support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + // Support: Webkit + // "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + }; + } +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +var isSimple = /^.[^:#\[\.,]*$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + cur = ret.push( cur ); + break; + } + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( isSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
      ", "
      " ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
      " ], + tr: [ 2, "", "
      " ], + col: [ 2, "", "
      " ], + td: [ 3, "", "
      " ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
      ", "
      " ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var + // Snapshot the DOM in case .domManip sweeps something relevant into its fragment + args = jQuery.map( this, function( elem ) { + return [ elem.nextSibling, elem.parentNode ]; + }), + i = 0; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + var next = args[ i++ ], + parent = args[ i++ ]; + + if ( parent ) { + // Don't use the snapshot next if it has moved (#13810) + if ( next && next.parentNode !== parent ) { + next = this.nextSibling; + } + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + // Allow new content to include elements from the context set + }, true ); + + // Force removal if there was no new content (e.g., from empty arguments) + return i ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback, allowIntersection ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback, allowIntersection ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery._evalUrl( node.src ); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
      " && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + }, + + _evalUrl: function( url ) { + return jQuery.ajax({ + url: url, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } +}); +jQuery.fn.extend({ + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + } +}); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each(function() { + if ( isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("