From 4bc069382eb28a92f20d52680d16cb5b50bd492b Mon Sep 17 00:00:00 2001 From: Minal Shah Date: Fri, 27 Jan 2017 18:21:14 +0530 Subject: [PATCH] Angular with apis --- my-todolist/.gitignore | 4 +- my-todolist/app.js | 94 +- my-todolist/index.html | 29 + my-todolist/indexanother.html | 26 + my-todolist/js/appstar.js | 26 + my-todolist/js/controllers | 37 + my-todolist/js/services.js | 11 + .../lib/angular-resource-ui-router.min.js | 4539 +++++++++++++++++ my-todolist/lib/angular-resource.min.js | 15 + my-todolist/lib/angular.min.js | 330 ++ my-todolist/settings.js | 2 +- my-todolist/todo.css | 4 + my-todolist/todo.js | 28 + 13 files changed, 5079 insertions(+), 66 deletions(-) create mode 100644 my-todolist/index.html create mode 100644 my-todolist/indexanother.html create mode 100644 my-todolist/js/appstar.js create mode 100644 my-todolist/js/controllers create mode 100644 my-todolist/js/services.js create mode 100644 my-todolist/lib/angular-resource-ui-router.min.js create mode 100644 my-todolist/lib/angular-resource.min.js create mode 100644 my-todolist/lib/angular.min.js create mode 100644 my-todolist/todo.css create mode 100644 my-todolist/todo.js diff --git a/my-todolist/.gitignore b/my-todolist/.gitignore index ca6ad34..debc15c 100644 --- a/my-todolist/.gitignore +++ b/my-todolist/.gitignore @@ -1,3 +1,5 @@ node_modules/ + + .idea -uploads/ +uploads diff --git a/my-todolist/app.js b/my-todolist/app.js index 57796cd..b18e57e 100644 --- a/my-todolist/app.js +++ b/my-todolist/app.js @@ -1,30 +1,25 @@ var express = require('express'); var multer=require('multer'); -var session = require('cookie-session'); +var session = require('cookie-session'); // Loads the piece of middleware for sessions var bodyParser = require('body-parser');// Loads the piece of middleware for managing the settings var mongoose=require('mongoose'); var validator=require('validator'); var fs= require('fs'); mongoose.connect('mongodb://localhost:27017/test'); var user=require('./model/user'); -//nick.save(function(err){console.log('user saved successfully')}) var morgan=require('morgan'); var jwt=require('jsonwebtoken'); var config=require('./config'); var urlencodedParser = bodyParser.urlencoded({ extended: false }); -//var fs = require('fs'); var app = express(); var Task=require('./model/task'); var Step=require('./model/step'); -//console.log(Step); var router=express.Router(); var ObjectId = require('mongodb').ObjectID; -// var buf = new Buffer(1024); var id,id1=0; var arr=[],arr1=[],arr2=[],array1=[],array=[],arr3=[],arr4=[]; var gfs; var token; - //var myo=new Object(); /* If there is no to do list in the session, we create an empty one in the form of an array before continuing */ app.use('/uploads',express.static(__dirname+"/upload")); @@ -48,17 +43,15 @@ app.use(function(req, res, next){ }); function addme(n,m,res) { - //var addme=require("./Routes/addme")(n,m,res); + Task.find({}, "-_id Todo", function (err, task) { arr = task; for (var i in arr) { - //console.log(arr[i]); var myo = new Object(); myo = arr[i]; arr1[i] = myo.Todo; - //console.log(myo.Todo); } - //console.log(arr[1]); + }).skip(n).limit(m); Task.find({}, "-_id Memberassigned", function (err, task) { arr = task; @@ -88,7 +81,6 @@ function addme(n,m,res) Task.find({}, "-_id Priority", function (err, task) { arr = task; - //arr.forEach(); for (var i in arr) { //console.log(arr[i]); var myo = new Object(); @@ -107,7 +99,7 @@ function addme(n,m,res) } /* /* The to do list and the form are displayed */ -router.get('/todo', function(req, res,next) { +router.get('/todo', function(req, res) { // var token = req.body.token || req.query.token || req.headers['x-access-token']; if(token) { @@ -126,40 +118,37 @@ addme(0,10,res); router.get('/todo/login',function(req,res) { res.render('login.ejs'); }); -router.post('/todo/login/', urlencodedParser, +router.post('/todo/login/', function(req,res){ - user.findOne({'name':req.body.member},function(err,user){ - console.log(user); + user.findOne({'name':req.body.member,'admin':true},function(err,user){ if(!user) - { res.json({success:false,message:'Authentication failed.user not found'}) - } + { + user.findOne({'name':req.body.member,'admin':false},function(err,user){ + if(!user) { - else if(user){ - console.log(user); - console.log(user.password); - console.log(req.body.password); - //req.session.name = user; - //res.redirect('/todo/member'); - //res.json({success:false,message:'Authentication failed.user not found'}); + res.json({success:false,message:'Authentication failed.user not found'}) + }else if(user){ + if(user.password!=req.body.password) + { + res.json({success:false,message:'Authentication failed.Wrong password'}) + }} + else + { + req.session.name = user; + res.redirect('/todo/member'); + } + }); + }else if(user){ if(user.password!=req.body.password) { res.json({success:false,message:'Authentication failed.Wrong password'}) }else{ - if(user.admin==true) { - - - var secret = config.secret; - console.log(secret); - var token1 = jwt.sign(user, app.get('superSecret'), { - expiresIn: 1440 - }); - token = token1; - res.redirect('/api/todo'); - } - else{ - res.redirect('/todo/member'); - } + var token1=jwt.sign(user,app.get('supersecret'),{ + expiresInMinutes:1440 + }); + token=token1; + res.redirect('/todo') } } @@ -199,20 +188,13 @@ router.get('/todo1', function(req, res) { }); /* Adding an item to the to do list */ router.post('/todo/add/', urlencodedParser, function(req, res) { - - //console.log(step); if (req.body.newtodo != '') { - // for(var i in req.body.) var task = new Task(); var step= new Step(); - - //console.log(req.body.hid); for (var i in req.body.hid) { var a=req.body.Step; - //console.log(req.body.Step); step.Steps=a; - //step.Todo=task.Todo; } task.Todo = req.body.newtodo; console.log(req.body.newtodo); @@ -236,9 +218,6 @@ router.post('/todo/add/', urlencodedParser, function(req, res) { upload(req,res,function(err){ if(err) { return res.end(err); - //res.end("error uploading file")} - // res.end("file is uploaded"); - } } ); @@ -268,8 +247,6 @@ router.post('/todo/add/', urlencodedParser, function(req, res) { if (err) { throw err; } - console.log(task); - //res.json({message:'Bear updated!'}); }); } ); @@ -278,23 +255,14 @@ router.post('/todo/add/', urlencodedParser, function(req, res) { else { res.send("sorry no data updated"); - //alert("No data updated"); res.redirect('/api/todo1') }}); -router.get('*', function (req, res) { - res.sendFile(__dirname + '/public/index.html'); - // load the single view file (angular will handle the page changes on the front-end) -}); /* Deletes an item from the to do list */ router.get('/todo/delete/:id', function(req, res) { console.log(req.params.id); id=req.params.id; Task.findByIdAndRemove(req.params.id,function(err,tasks){console.log(tasks);}); - -//Task.remove(); - //console.log(a); - res.redirect('/api/todo'); }); @@ -308,19 +276,17 @@ router.get('/todo/delete/:id', function(req, res) { /* Redirects to the to do list if the page requested is not found */ app.use('/api',router); app.use(function(req, res, next) { -console.log(id1); + if(id1==0) { - router.route('/api/todo'); + res.redirect('/api/todo/login'); + } else { res.redirect('/api/todo1'); } -//next(); }); - - app.set('superSecret',config.secret); app.use(morgan('dev')); app.listen(8010); \ No newline at end of file diff --git a/my-todolist/index.html b/my-todolist/index.html new file mode 100644 index 0000000..27f806b --- /dev/null +++ b/my-todolist/index.html @@ -0,0 +1,29 @@ + + + + + + + + +

Todo

+
+ {{todoList.remaining()}} of {{todoList.todos.length}} remaining +[ archive ] + +
+ + +
+
+ + \ No newline at end of file diff --git a/my-todolist/indexanother.html b/my-todolist/indexanother.html new file mode 100644 index 0000000..d63eb24 --- /dev/null +++ b/my-todolist/indexanother.html @@ -0,0 +1,26 @@ + + + + + + Todo + + +
+
+ Todo App +
+
+ Home +
+ +
+ + + + + + + + \ No newline at end of file diff --git a/my-todolist/js/appstar.js b/my-todolist/js/appstar.js new file mode 100644 index 0000000..70ac86b --- /dev/null +++ b/my-todolist/js/appstar.js @@ -0,0 +1,26 @@ +angular.module('movieApp',['uirouter','ngResource','TodoApp.controllers','TodoApp.services']); + +angular.module('TodoApp').config(function($stateProvider){ + $stateProvider.state('Todo',{ + url:'/todo', + templateurl:'views/todo.ejs', + controller:'TodoController' + }) + .state('viewTodo',{ + url:'/todo/delete/:id', + templateurl:'views/todo.ejs', + controller:'TodoViewController' + }) + .state('AddTodo',{ + url:'/todo/add', + templateurl:'views/todo.ejs', + controller:'TodoCreateController' + }) + .state('editTodo',{ + url:'/todo/update/', + templateurl:'views/todo1.ejs', + controller:'TodoEditController' + }); +}).run(function ($state) { + $state.go('Todo'); +}); \ No newline at end of file diff --git a/my-todolist/js/controllers b/my-todolist/js/controllers new file mode 100644 index 0000000..f591699 --- /dev/null +++ b/my-todolist/js/controllers @@ -0,0 +1,37 @@ +/** + * Created by lcom64 on 27/1/17. + */ +angular.module('TodoApp.controllers',[]).controller('TodoListController',function ($scope,$state,popupService,$window,Todo){ + + $scope.Todo=Todo.query(); + $scope.deleteTodo=function(Todo) + { + if(popupService.showPopup("Really delete this?")) + { + Todo.$delete(function(){ + $window.location.href=''; + }); + } + }; +}).controller('TodoListController',function($scope,$stateParams,Todo){ + $scope.Todo=Todo.get({id:$stateParams}); + +}).controller('TodoCreateController',function($scope,$state,$stateParams,Todo){ + $scope.Todo=new Todo(); + + $scope.addTodo =function(){ + $scope.Todo.$save(function(){ + $state.go('Todo'); + }); + }; +}).controller('TodoEditController',function($scope,$state,$stateParams,Todo){ + $scope.updateTodo=function(){ + $scope.Todo.$update(function(){ + state.go('Todo'); + }); + }; + $scope.loadTodo=function(){ + $scope.Todo(Todo.get({id:$stateParams.id})) + }; + $scope.loadTodo(); +}) \ No newline at end of file diff --git a/my-todolist/js/services.js b/my-todolist/js/services.js new file mode 100644 index 0000000..f55c5c9 --- /dev/null +++ b/my-todolist/js/services.js @@ -0,0 +1,11 @@ +/** + * Created by lcom64 on 27/1/17. + */ +angular.module('Todo.services',[]).factory('Todo',function($resource){ + return $resource('http://localhost:8010/api/todo/:id',{ id:'@_id'},{ + update: { + method: 'PUT' + } + + }) +}); \ No newline at end of file diff --git a/my-todolist/lib/angular-resource-ui-router.min.js b/my-todolist/lib/angular-resource-ui-router.min.js new file mode 100644 index 0000000..752d605 --- /dev/null +++ b/my-todolist/lib/angular-resource-ui-router.min.js @@ -0,0 +1,4539 @@ +/** + * State-based routing for AngularJS + * @version v0.2.18 + * @link http://angular-ui.github.com/ + * @license MIT License, http://www.opensource.org/licenses/MIT + */ + +/* commonjs package manager support (eg componentjs) */ +if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){ + module.exports = 'ui.router'; +} + +(function (window, angular, undefined) { +/*jshint globalstrict:true*/ +/*global angular:false*/ +'use strict'; + +var isDefined = angular.isDefined, + isFunction = angular.isFunction, + isString = angular.isString, + isObject = angular.isObject, + isArray = angular.isArray, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + toJson = angular.toJson; + +function inherit(parent, extra) { + return extend(new (extend(function() {}, { prototype: parent }))(), extra); +} + +function merge(dst) { + forEach(arguments, function(obj) { + if (obj !== dst) { + forEach(obj, function(value, key) { + if (!dst.hasOwnProperty(key)) dst[key] = value; + }); + } + }); + return dst; +} + +/** + * Finds the common ancestor path between two states. + * + * @param {Object} first The first state. + * @param {Object} second The second state. + * @return {Array} Returns an array of state names in descending order, not including the root. + */ +function ancestors(first, second) { + var path = []; + + for (var n in first.path) { + if (first.path[n] !== second.path[n]) break; + path.push(first.path[n]); + } + return path; +} + +/** + * IE8-safe wrapper for `Object.keys()`. + * + * @param {Object} object A JavaScript object. + * @return {Array} Returns the keys of the object as an array. + */ +function objectKeys(object) { + if (Object.keys) { + return Object.keys(object); + } + var result = []; + + forEach(object, function(val, key) { + result.push(key); + }); + return result; +} + +/** + * IE8-safe wrapper for `Array.prototype.indexOf()`. + * + * @param {Array} array A JavaScript array. + * @param {*} value A value to search the array for. + * @return {Number} Returns the array index value of `value`, or `-1` if not present. + */ +function indexOf(array, value) { + if (Array.prototype.indexOf) { + return array.indexOf(value, Number(arguments[2]) || 0); + } + var len = array.length >>> 0, from = Number(arguments[2]) || 0; + from = (from < 0) ? Math.ceil(from) : Math.floor(from); + + if (from < 0) from += len; + + for (; from < len; from++) { + if (from in array && array[from] === value) return from; + } + return -1; +} + +/** + * Merges a set of parameters with all parameters inherited between the common parents of the + * current state and a given destination state. + * + * @param {Object} currentParams The value of the current state parameters ($stateParams). + * @param {Object} newParams The set of parameters which will be composited with inherited params. + * @param {Object} $current Internal definition of object representing the current state. + * @param {Object} $to Internal definition of object representing state to transition to. + */ +function inheritParams(currentParams, newParams, $current, $to) { + var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = []; + + for (var i in parents) { + if (!parents[i] || !parents[i].params) continue; + parentParams = objectKeys(parents[i].params); + if (!parentParams.length) continue; + + for (var j in parentParams) { + if (indexOf(inheritList, parentParams[j]) >= 0) continue; + inheritList.push(parentParams[j]); + inherited[parentParams[j]] = currentParams[parentParams[j]]; + } + } + return extend({}, inherited, newParams); +} + +/** + * Performs a non-strict comparison of the subset of two objects, defined by a list of keys. + * + * @param {Object} a The first object. + * @param {Object} b The second object. + * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified, + * it defaults to the list of keys in `a`. + * @return {Boolean} Returns `true` if the keys match, otherwise `false`. + */ +function equalForKeys(a, b, keys) { + if (!keys) { + keys = []; + for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility + } + + for (var i=0; i + * + * + * + * + * + * + * + * + * + * + * + * + */ +angular.module('ui.router', ['ui.router.state']); + +angular.module('ui.router.compat', ['ui.router']); + +/** + * @ngdoc object + * @name ui.router.util.$resolve + * + * @requires $q + * @requires $injector + * + * @description + * Manages resolution of (acyclic) graphs of promises. + */ +$Resolve.$inject = ['$q', '$injector']; +function $Resolve( $q, $injector) { + + var VISIT_IN_PROGRESS = 1, + VISIT_DONE = 2, + NOTHING = {}, + NO_DEPENDENCIES = [], + NO_LOCALS = NOTHING, + NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING }); + + + /** + * @ngdoc function + * @name ui.router.util.$resolve#study + * @methodOf ui.router.util.$resolve + * + * @description + * Studies a set of invocables that are likely to be used multiple times. + *
+   * $resolve.study(invocables)(locals, parent, self)
+   * 
+ * is equivalent to + *
+   * $resolve.resolve(invocables, locals, parent, self)
+   * 
+ * but the former is more efficient (in fact `resolve` just calls `study` + * internally). + * + * @param {object} invocables Invocable objects + * @return {function} a function to pass in locals, parent and self + */ + this.study = function (invocables) { + if (!isObject(invocables)) throw new Error("'invocables' must be an object"); + var invocableKeys = objectKeys(invocables || {}); + + // Perform a topological sort of invocables to build an ordered plan + var plan = [], cycle = [], visited = {}; + function visit(value, key) { + if (visited[key] === VISIT_DONE) return; + + cycle.push(key); + if (visited[key] === VISIT_IN_PROGRESS) { + cycle.splice(0, indexOf(cycle, key)); + throw new Error("Cyclic dependency: " + cycle.join(" -> ")); + } + visited[key] = VISIT_IN_PROGRESS; + + if (isString(value)) { + plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); + } else { + var params = $injector.annotate(value); + forEach(params, function (param) { + if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param); + }); + plan.push(key, value, params); + } + + cycle.pop(); + visited[key] = VISIT_DONE; + } + forEach(invocables, visit); + invocables = cycle = visited = null; // plan is all that's required + + function isResolve(value) { + return isObject(value) && value.then && value.$$promises; + } + + return function (locals, parent, self) { + if (isResolve(locals) && self === undefined) { + self = parent; parent = locals; locals = null; + } + if (!locals) locals = NO_LOCALS; + else if (!isObject(locals)) { + throw new Error("'locals' must be an object"); + } + if (!parent) parent = NO_PARENT; + else if (!isResolve(parent)) { + throw new Error("'parent' must be a promise returned by $resolve.resolve()"); + } + + // To complete the overall resolution, we have to wait for the parent + // promise and for the promise for each invokable in our plan. + var resolution = $q.defer(), + result = resolution.promise, + promises = result.$$promises = {}, + values = extend({}, locals), + wait = 1 + plan.length/3, + merged = false; + + function done() { + // Merge parent values we haven't got yet and publish our own $$values + if (!--wait) { + if (!merged) merge(values, parent.$$values); + result.$$values = values; + result.$$promises = result.$$promises || true; // keep for isResolve() + delete result.$$inheritedValues; + resolution.resolve(values); + } + } + + function fail(reason) { + result.$$failure = reason; + resolution.reject(reason); + } + + // Short-circuit if parent has already failed + if (isDefined(parent.$$failure)) { + fail(parent.$$failure); + return result; + } + + if (parent.$$inheritedValues) { + merge(values, omit(parent.$$inheritedValues, invocableKeys)); + } + + // Merge parent values if the parent has already resolved, or merge + // parent promises and wait if the parent resolve is still in progress. + extend(promises, parent.$$promises); + if (parent.$$values) { + merged = merge(values, omit(parent.$$values, invocableKeys)); + result.$$inheritedValues = omit(parent.$$values, invocableKeys); + done(); + } else { + if (parent.$$inheritedValues) { + result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys); + } + parent.then(done, fail); + } + + // Process each invocable in the plan, but ignore any where a local of the same name exists. + for (var i=0, ii=plan.length; i} The template html as a string, or a promise + * for that string. + */ + this.fromUrl = function (url, params) { + if (isFunction(url)) url = url(params); + if (url == null) return null; + else return $http + .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }}) + .then(function(response) { return response.data; }); + }; + + /** + * @ngdoc function + * @name ui.router.util.$templateFactory#fromProvider + * @methodOf ui.router.util.$templateFactory + * + * @description + * Creates a template by invoking an injectable provider function. + * + * @param {Function} provider Function to invoke via `$injector.invoke` + * @param {Object} params Parameters for the template. + * @param {Object} locals Locals to pass to `invoke`. Defaults to + * `{ params: params }`. + * @return {string|Promise.} The template html as a string, or a promise + * for that string. + */ + this.fromProvider = function (provider, params, locals) { + return $injector.invoke(provider, null, locals || { params: params }); + }; +} + +angular.module('ui.router.util').service('$templateFactory', $TemplateFactory); + +var $$UMFP; // reference to $UrlMatcherFactoryProvider + +/** + * @ngdoc object + * @name ui.router.util.type:UrlMatcher + * + * @description + * Matches URLs against patterns and extracts named parameters from the path or the search + * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list + * of search parameters. Multiple search parameter names are separated by '&'. Search parameters + * do not influence whether or not a URL is matched, but their values are passed through into + * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}. + * + * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace + * syntax, which optionally allows a regular expression for the parameter to be specified: + * + * * `':'` name - colon placeholder + * * `'*'` name - catch-all placeholder + * * `'{' name '}'` - curly placeholder + * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the + * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash. + * + * Parameter names may contain only word characters (latin letters, digits, and underscore) and + * must be unique within the pattern (across both path and search parameters). For colon + * placeholders or curly placeholders without an explicit regexp, a path parameter matches any + * number of characters other than '/'. For catch-all placeholders the path parameter matches + * any number of characters. + * + * Examples: + * + * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for + * trailing slashes, and patterns have to match the entire path, not just a prefix. + * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or + * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. + * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax. + * * `'/user/{id:[^/]*}'` - Same as the previous example. + * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id + * parameter consists of 1 to 8 hex digits. + * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the + * path into the parameter 'path'. + * * `'/files/*path'` - ditto. + * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined + * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start + * + * @param {string} pattern The pattern to compile into a matcher. + * @param {Object} config A configuration object hash: + * @param {Object=} parentMatcher Used to concatenate the pattern/config onto + * an existing UrlMatcher + * + * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`. + * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`. + * + * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any + * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns + * non-null) will start with this prefix. + * + * @property {string} source The pattern that was passed into the constructor + * + * @property {string} sourcePath The path portion of the source property + * + * @property {string} sourceSearch The search portion of the source property + * + * @property {string} regex The constructed regex that will be used to match against the url when + * it is time to determine which url will match. + * + * @returns {Object} New `UrlMatcher` object + */ +function UrlMatcher(pattern, config, parentMatcher) { + config = extend({ params: {} }, isObject(config) ? config : {}); + + // Find all placeholders and create a compiled pattern, using either classic or curly syntax: + // '*' name + // ':' name + // '{' name '}' + // '{' name ':' regexp '}' + // The regular expression is somewhat complicated due to the need to allow curly braces + // inside the regular expression. The placeholder regexp breaks down as follows: + // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case) + // \{([\w\[\]]+)(?:\:\s*( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case + // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either + // [^{}\\]+ - anything other than curly braces or backslash + // \\. - a backslash escape + // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms + var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, + searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, + compiled = '^', last = 0, m, + segments = this.segments = [], + parentParams = parentMatcher ? parentMatcher.params : {}, + params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(), + paramNames = []; + + function addParameter(id, type, config, location) { + paramNames.push(id); + if (parentParams[id]) return parentParams[id]; + if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); + if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); + params[id] = new $$UMFP.Param(id, type, config, location); + return params[id]; + } + + function quoteRegExp(string, pattern, squash, optional) { + var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); + if (!pattern) return result; + switch(squash) { + case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break; + case true: + result = result.replace(/\/$/, ''); + surroundPattern = ['(?:\/(', ')|\/)?']; + break; + default: surroundPattern = ['(' + squash + "|", ')?']; break; + } + return result + surroundPattern[0] + pattern + surroundPattern[1]; + } + + this.source = pattern; + + // Split into static segments separated by path parameter placeholders. + // The number of segments is always 1 more than the number of parameters. + function matchDetails(m, isSearch) { + var id, regexp, segment, type, cfg, arrayMode; + id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null + cfg = config.params[id]; + segment = pattern.substring(last, m.index); + regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null); + + if (regexp) { + type = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) }); + } + + return { + id: id, regexp: regexp, segment: segment, type: type, cfg: cfg + }; + } + + var p, param, segment; + while ((m = placeholder.exec(pattern))) { + p = matchDetails(m, false); + if (p.segment.indexOf('?') >= 0) break; // we're into the search part + + param = addParameter(p.id, p.type, p.cfg, "path"); + compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional); + segments.push(p.segment); + last = placeholder.lastIndex; + } + segment = pattern.substring(last); + + // Find any search parameter names and remove them from the last segment + var i = segment.indexOf('?'); + + if (i >= 0) { + var search = this.sourceSearch = segment.substring(i); + segment = segment.substring(0, i); + this.sourcePath = pattern.substring(0, last + i); + + if (search.length > 0) { + last = 0; + while ((m = searchPlaceholder.exec(search))) { + p = matchDetails(m, true); + param = addParameter(p.id, p.type, p.cfg, "search"); + last = placeholder.lastIndex; + // check if ?& + } + } + } else { + this.sourcePath = pattern; + this.sourceSearch = ''; + } + + compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$'; + segments.push(segment); + + this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined); + this.prefix = segments[0]; + this.$$paramNames = paramNames; +} + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#concat + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Returns a new matcher for a pattern constructed by appending the path part and adding the + * search parameters of the specified pattern to this pattern. The current pattern is not + * modified. This can be understood as creating a pattern for URLs that are relative to (or + * suffixes of) the current pattern. + * + * @example + * The following two matchers are equivalent: + *
+ * new UrlMatcher('/user/{id}?q').concat('/details?date');
+ * new UrlMatcher('/user/{id}/details?q&date');
+ * 
+ * + * @param {string} pattern The pattern to append. + * @param {Object} config An object hash of the configuration for the matcher. + * @returns {UrlMatcher} A matcher for the concatenated pattern. + */ +UrlMatcher.prototype.concat = function (pattern, config) { + // Because order of search parameters is irrelevant, we can add our own search + // parameters to the end of the new pattern. Parse the new pattern by itself + // and then join the bits together, but it's much easier to do this on a string level. + var defaultConfig = { + caseInsensitive: $$UMFP.caseInsensitive(), + strict: $$UMFP.strictMode(), + squash: $$UMFP.defaultSquashPolicy() + }; + return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this); +}; + +UrlMatcher.prototype.toString = function () { + return this.source; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#exec + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Tests the specified path against this matcher, and returns an object containing the captured + * parameter values, or null if the path does not match. The returned object contains the values + * of any search parameters that are mentioned in the pattern, but their value may be null if + * they are not present in `searchParams`. This means that search parameters are always treated + * as optional. + * + * @example + *
+ * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
+ *   x: '1', q: 'hello'
+ * });
+ * // returns { id: 'bob', q: 'hello', r: null }
+ * 
+ * + * @param {string} path The URL path to match, e.g. `$location.path()`. + * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. + * @returns {Object} The captured parameter values. + */ +UrlMatcher.prototype.exec = function (path, searchParams) { + var m = this.regexp.exec(path); + if (!m) return null; + searchParams = searchParams || {}; + + var paramNames = this.parameters(), nTotal = paramNames.length, + nPath = this.segments.length - 1, + values = {}, i, j, cfg, paramName; + + if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); + + function decodePathArray(string) { + function reverseString(str) { return str.split("").reverse().join(""); } + function unquoteDashes(str) { return str.replace(/\\-/g, "-"); } + + var split = reverseString(string).split(/-(?!\\)/); + var allReversed = map(split, reverseString); + return map(allReversed, unquoteDashes).reverse(); + } + + var param, paramVal; + for (i = 0; i < nPath; i++) { + paramName = paramNames[i]; + param = this.params[paramName]; + paramVal = m[i+1]; + // if the param value matches a pre-replace pair, replace the value before decoding. + for (j = 0; j < param.replace.length; j++) { + if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; + } + if (paramVal && param.array === true) paramVal = decodePathArray(paramVal); + if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); + values[paramName] = param.value(paramVal); + } + for (/**/; i < nTotal; i++) { + paramName = paramNames[i]; + values[paramName] = this.params[paramName].value(searchParams[paramName]); + param = this.params[paramName]; + paramVal = searchParams[paramName]; + for (j = 0; j < param.replace.length; j++) { + if (param.replace[j].from === paramVal) paramVal = param.replace[j].to; + } + if (isDefined(paramVal)) paramVal = param.type.decode(paramVal); + values[paramName] = param.value(paramVal); + } + + return values; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#parameters + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Returns the names of all path and search parameters of this pattern in an unspecified order. + * + * @returns {Array.} An array of parameter names. Must be treated as read-only. If the + * pattern has no parameters, an empty array is returned. + */ +UrlMatcher.prototype.parameters = function (param) { + if (!isDefined(param)) return this.$$paramNames; + return this.params[param] || null; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#validates + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Checks an object hash of parameters to validate their correctness according to the parameter + * types of this `UrlMatcher`. + * + * @param {Object} params The object hash of parameters to validate. + * @returns {boolean} Returns `true` if `params` validates, otherwise `false`. + */ +UrlMatcher.prototype.validates = function (params) { + return this.params.$$validates(params); +}; + +/** + * @ngdoc function + * @name ui.router.util.type:UrlMatcher#format + * @methodOf ui.router.util.type:UrlMatcher + * + * @description + * Creates a URL that matches this pattern by substituting the specified values + * for the path and search parameters. Null values for path parameters are + * treated as empty strings. + * + * @example + *
+ * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
+ * // returns '/user/bob?q=yes'
+ * 
+ * + * @param {Object} values the values to substitute for the parameters in this pattern. + * @returns {string} the formatted URL (path and optionally search part). + */ +UrlMatcher.prototype.format = function (values) { + values = values || {}; + var segments = this.segments, params = this.parameters(), paramset = this.params; + if (!this.validates(values)) return null; + + var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0]; + + function encodeDashes(str) { // Replace dashes with encoded "\-" + return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); }); + } + + for (i = 0; i < nTotal; i++) { + var isPathParam = i < nPath; + var name = params[i], param = paramset[name], value = param.value(values[name]); + var isDefaultValue = param.isOptional && param.type.equals(param.value(), value); + var squash = isDefaultValue ? param.squash : false; + var encoded = param.type.encode(value); + + if (isPathParam) { + var nextSegment = segments[i + 1]; + var isFinalPathParam = i + 1 === nPath; + + if (squash === false) { + if (encoded != null) { + if (isArray(encoded)) { + result += map(encoded, encodeDashes).join("-"); + } else { + result += encodeURIComponent(encoded); + } + } + result += nextSegment; + } else if (squash === true) { + var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/; + result += nextSegment.match(capture)[1]; + } else if (isString(squash)) { + result += squash + nextSegment; + } + + if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1); + } else { + if (encoded == null || (isDefaultValue && squash !== false)) continue; + if (!isArray(encoded)) encoded = [ encoded ]; + if (encoded.length === 0) continue; + encoded = map(encoded, encodeURIComponent).join('&' + name + '='); + result += (search ? '&' : '?') + (name + '=' + encoded); + search = true; + } + } + + return result; +}; + +/** + * @ngdoc object + * @name ui.router.util.type:Type + * + * @description + * Implements an interface to define custom parameter types that can be decoded from and encoded to + * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`} + * objects when matching or formatting URLs, or comparing or validating parameter values. + * + * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more + * information on registering custom types. + * + * @param {Object} config A configuration object which contains the custom type definition. The object's + * properties will override the default methods and/or pattern in `Type`'s public interface. + * @example + *
+ * {
+ *   decode: function(val) { return parseInt(val, 10); },
+ *   encode: function(val) { return val && val.toString(); },
+ *   equals: function(a, b) { return this.is(a) && a === b; },
+ *   is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
+ *   pattern: /\d+/
+ * }
+ * 
+ * + * @property {RegExp} pattern The regular expression pattern used to match values of this type when + * coming from a substring of a URL. + * + * @returns {Object} Returns a new `Type` object. + */ +function Type(config) { + extend(this, config); +} + +/** + * @ngdoc function + * @name ui.router.util.type:Type#is + * @methodOf ui.router.util.type:Type + * + * @description + * Detects whether a value is of a particular type. Accepts a native (decoded) value + * and determines whether it matches the current `Type` object. + * + * @param {*} val The value to check. + * @param {string} key Optional. If the type check is happening in the context of a specific + * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the + * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects. + * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`. + */ +Type.prototype.is = function(val, key) { + return true; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:Type#encode + * @methodOf ui.router.util.type:Type + * + * @description + * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the + * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it + * only needs to be a representation of `val` that has been coerced to a string. + * + * @param {*} val The value to encode. + * @param {string} key The name of the parameter in which `val` is stored. Can be used for + * meta-programming of `Type` objects. + * @returns {string} Returns a string representation of `val` that can be encoded in a URL. + */ +Type.prototype.encode = function(val, key) { + return val; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:Type#decode + * @methodOf ui.router.util.type:Type + * + * @description + * Converts a parameter value (from URL string or transition param) to a custom/native value. + * + * @param {string} val The URL parameter value to decode. + * @param {string} key The name of the parameter in which `val` is stored. Can be used for + * meta-programming of `Type` objects. + * @returns {*} Returns a custom representation of the URL parameter value. + */ +Type.prototype.decode = function(val, key) { + return val; +}; + +/** + * @ngdoc function + * @name ui.router.util.type:Type#equals + * @methodOf ui.router.util.type:Type + * + * @description + * Determines whether two decoded values are equivalent. + * + * @param {*} a A value to compare against. + * @param {*} b A value to compare against. + * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`. + */ +Type.prototype.equals = function(a, b) { + return a == b; +}; + +Type.prototype.$subPattern = function() { + var sub = this.pattern.toString(); + return sub.substr(1, sub.length - 2); +}; + +Type.prototype.pattern = /.*/; + +Type.prototype.toString = function() { return "{Type:" + this.name + "}"; }; + +/** Given an encoded string, or a decoded object, returns a decoded object */ +Type.prototype.$normalize = function(val) { + return this.is(val) ? val : this.decode(val); +}; + +/* + * Wraps an existing custom Type as an array of Type, depending on 'mode'. + * e.g.: + * - urlmatcher pattern "/path?{queryParam[]:int}" + * - url: "/path?queryParam=1&queryParam=2 + * - $stateParams.queryParam will be [1, 2] + * if `mode` is "auto", then + * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1 + * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2] + */ +Type.prototype.$asArray = function(mode, isSearch) { + if (!mode) return this; + if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only"); + + function ArrayType(type, mode) { + function bindTo(type, callbackName) { + return function() { + return type[callbackName].apply(type, arguments); + }; + } + + // Wrap non-array value as array + function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); } + // Unwrap array value for "auto" mode. Return undefined for empty array. + function arrayUnwrap(val) { + switch(val.length) { + case 0: return undefined; + case 1: return mode === "auto" ? val[0] : val; + default: return val; + } + } + function falsey(val) { return !val; } + + // Wraps type (.is/.encode/.decode) functions to operate on each value of an array + function arrayHandler(callback, allTruthyMode) { + return function handleArray(val) { + if (isArray(val) && val.length === 0) return val; + val = arrayWrap(val); + var result = map(val, callback); + if (allTruthyMode === true) + return filter(result, falsey).length === 0; + return arrayUnwrap(result); + }; + } + + // Wraps type (.equals) functions to operate on each value of an array + function arrayEqualsHandler(callback) { + return function handleArray(val1, val2) { + var left = arrayWrap(val1), right = arrayWrap(val2); + if (left.length !== right.length) return false; + for (var i = 0; i < left.length; i++) { + if (!callback(left[i], right[i])) return false; + } + return true; + }; + } + + this.encode = arrayHandler(bindTo(type, 'encode')); + this.decode = arrayHandler(bindTo(type, 'decode')); + this.is = arrayHandler(bindTo(type, 'is'), true); + this.equals = arrayEqualsHandler(bindTo(type, 'equals')); + this.pattern = type.pattern; + this.$normalize = arrayHandler(bindTo(type, '$normalize')); + this.name = type.name; + this.$arrayMode = mode; + } + + return new ArrayType(this, mode); +}; + + + +/** + * @ngdoc object + * @name ui.router.util.$urlMatcherFactory + * + * @description + * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory + * is also available to providers under the name `$urlMatcherFactoryProvider`. + */ +function $UrlMatcherFactory() { + $$UMFP = this; + + var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false; + + // Use tildes to pre-encode slashes. + // If the slashes are simply URLEncoded, the browser can choose to pre-decode them, + // and bidirectional encoding/decoding fails. + // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character + function valToString(val) { return val != null ? val.toString().replace(/~/g, "~~").replace(/\//g, "~2F") : val; } + function valFromString(val) { return val != null ? val.toString().replace(/~2F/g, "/").replace(/~~/g, "~") : val; } + + var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = { + "string": { + encode: valToString, + decode: valFromString, + // TODO: in 1.0, make string .is() return false if value is undefined/null by default. + // In 0.2.x, string params are optional by default for backwards compat + is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; }, + pattern: /[^/]*/ + }, + "int": { + encode: valToString, + decode: function(val) { return parseInt(val, 10); }, + is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; }, + pattern: /\d+/ + }, + "bool": { + encode: function(val) { return val ? 1 : 0; }, + decode: function(val) { return parseInt(val, 10) !== 0; }, + is: function(val) { return val === true || val === false; }, + pattern: /0|1/ + }, + "date": { + encode: function (val) { + if (!this.is(val)) + return undefined; + return [ val.getFullYear(), + ('0' + (val.getMonth() + 1)).slice(-2), + ('0' + val.getDate()).slice(-2) + ].join("-"); + }, + decode: function (val) { + if (this.is(val)) return val; + var match = this.capture.exec(val); + return match ? new Date(match[1], match[2] - 1, match[3]) : undefined; + }, + is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); }, + equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); }, + pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/, + capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/ + }, + "json": { + encode: angular.toJson, + decode: angular.fromJson, + is: angular.isObject, + equals: angular.equals, + pattern: /[^/]*/ + }, + "any": { // does not encode/decode + encode: angular.identity, + decode: angular.identity, + equals: angular.equals, + pattern: /.*/ + } + }; + + function getDefaultConfig() { + return { + strict: isStrictMode, + caseInsensitive: isCaseInsensitive + }; + } + + function isInjectable(value) { + return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1]))); + } + + /** + * [Internal] Get the default value of a parameter, which may be an injectable function. + */ + $UrlMatcherFactory.$$getDefaultValue = function(config) { + if (!isInjectable(config.value)) return config.value; + if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); + return injector.invoke(config.value); + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#caseInsensitive + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Defines whether URL matching should be case sensitive (the default behavior), or not. + * + * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`; + * @returns {boolean} the current value of caseInsensitive + */ + this.caseInsensitive = function(value) { + if (isDefined(value)) + isCaseInsensitive = value; + return isCaseInsensitive; + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#strictMode + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Defines whether URLs should match trailing slashes, or not (the default behavior). + * + * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`. + * @returns {boolean} the current value of strictMode + */ + this.strictMode = function(value) { + if (isDefined(value)) + isStrictMode = value; + return isStrictMode; + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Sets the default behavior when generating or matching URLs with default parameter values. + * + * @param {string} value A string that defines the default parameter URL squashing behavior. + * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL + * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the + * parameter is surrounded by slashes, squash (remove) one slash from the URL + * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove) + * the parameter value from the URL and replace it with this string. + */ + this.defaultSquashPolicy = function(value) { + if (!isDefined(value)) return defaultSquashPolicy; + if (value !== true && value !== false && !isString(value)) + throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string"); + defaultSquashPolicy = value; + return value; + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#compile + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern. + * + * @param {string} pattern The URL pattern. + * @param {Object} config The config object hash. + * @returns {UrlMatcher} The UrlMatcher. + */ + this.compile = function (pattern, config) { + return new UrlMatcher(pattern, extend(getDefaultConfig(), config)); + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#isMatcher + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Returns true if the specified object is a `UrlMatcher`, or false otherwise. + * + * @param {Object} object The object to perform the type check against. + * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by + * implementing all the same methods. + */ + this.isMatcher = function (o) { + if (!isObject(o)) return false; + var result = true; + + forEach(UrlMatcher.prototype, function(val, name) { + if (isFunction(val)) { + result = result && (isDefined(o[name]) && isFunction(o[name])); + } + }); + return result; + }; + + /** + * @ngdoc function + * @name ui.router.util.$urlMatcherFactory#type + * @methodOf ui.router.util.$urlMatcherFactory + * + * @description + * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to + * generate URLs with typed parameters. + * + * @param {string} name The type name. + * @param {Object|Function} definition The type definition. See + * {@link ui.router.util.type:Type `Type`} for information on the values accepted. + * @param {Object|Function} definitionFn (optional) A function that is injected before the app + * runtime starts. The result of this function is merged into the existing `definition`. + * See {@link ui.router.util.type:Type `Type`} for information on the values accepted. + * + * @returns {Object} Returns `$urlMatcherFactoryProvider`. + * + * @example + * This is a simple example of a custom type that encodes and decodes items from an + * array, using the array index as the URL-encoded value: + * + *
+   * var list = ['John', 'Paul', 'George', 'Ringo'];
+   *
+   * $urlMatcherFactoryProvider.type('listItem', {
+   *   encode: function(item) {
+   *     // Represent the list item in the URL using its corresponding index
+   *     return list.indexOf(item);
+   *   },
+   *   decode: function(item) {
+   *     // Look up the list item by index
+   *     return list[parseInt(item, 10)];
+   *   },
+   *   is: function(item) {
+   *     // Ensure the item is valid by checking to see that it appears
+   *     // in the list
+   *     return list.indexOf(item) > -1;
+   *   }
+   * });
+   *
+   * $stateProvider.state('list', {
+   *   url: "/list/{item:listItem}",
+   *   controller: function($scope, $stateParams) {
+   *     console.log($stateParams.item);
+   *   }
+   * });
+   *
+   * // ...
+   *
+   * // Changes URL to '/list/3', logs "Ringo" to the console
+   * $state.go('list', { item: "Ringo" });
+   * 
+ * + * This is a more complex example of a type that relies on dependency injection to + * interact with services, and uses the parameter name from the URL to infer how to + * handle encoding and decoding parameter values: + * + *
+   * // Defines a custom type that gets a value from a service,
+   * // where each service gets different types of values from
+   * // a backend API:
+   * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
+   *
+   *   // Matches up services to URL parameter names
+   *   var services = {
+   *     user: Users,
+   *     post: Posts
+   *   };
+   *
+   *   return {
+   *     encode: function(object) {
+   *       // Represent the object in the URL using its unique ID
+   *       return object.id;
+   *     },
+   *     decode: function(value, key) {
+   *       // Look up the object by ID, using the parameter
+   *       // name (key) to call the correct service
+   *       return services[key].findById(value);
+   *     },
+   *     is: function(object, key) {
+   *       // Check that object is a valid dbObject
+   *       return angular.isObject(object) && object.id && services[key];
+   *     }
+   *     equals: function(a, b) {
+   *       // Check the equality of decoded objects by comparing
+   *       // their unique IDs
+   *       return a.id === b.id;
+   *     }
+   *   };
+   * });
+   *
+   * // In a config() block, you can then attach URLs with
+   * // type-annotated parameters:
+   * $stateProvider.state('users', {
+   *   url: "/users",
+   *   // ...
+   * }).state('users.item', {
+   *   url: "/{user:dbObject}",
+   *   controller: function($scope, $stateParams) {
+   *     // $stateParams.user will now be an object returned from
+   *     // the Users service
+   *   },
+   *   // ...
+   * });
+   * 
+ */ + this.type = function (name, definition, definitionFn) { + if (!isDefined(definition)) return $types[name]; + if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined."); + + $types[name] = new Type(extend({ name: name }, definition)); + if (definitionFn) { + typeQueue.push({ name: name, def: definitionFn }); + if (!enqueue) flushTypeQueue(); + } + return this; + }; + + // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s + function flushTypeQueue() { + while(typeQueue.length) { + var type = typeQueue.shift(); + if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime."); + angular.extend($types[type.name], injector.invoke(type.def)); + } + } + + // Register default types. Store them in the prototype of $types. + forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); }); + $types = inherit($types, {}); + + /* No need to document $get, since it returns this */ + this.$get = ['$injector', function ($injector) { + injector = $injector; + enqueue = false; + flushTypeQueue(); + + forEach(defaultTypes, function(type, name) { + if (!$types[name]) $types[name] = new Type(type); + }); + return this; + }]; + + this.Param = function Param(id, type, config, location) { + var self = this; + config = unwrapShorthand(config); + type = getType(config, type, location); + var arrayMode = getArrayMode(); + type = arrayMode ? type.$asArray(arrayMode, location === "search") : type; + if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined) + config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to "" + var isOptional = config.value !== undefined; + var squash = getSquashPolicy(config, isOptional); + var replace = getReplace(config, arrayMode, isOptional, squash); + + function unwrapShorthand(config) { + var keys = isObject(config) ? objectKeys(config) : []; + var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 && + indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1; + if (isShorthand) config = { value: config }; + config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; }; + return config; + } + + function getType(config, urlType, location) { + if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations."); + if (urlType) return urlType; + if (!config.type) return (location === "config" ? $types.any : $types.string); + + if (angular.isString(config.type)) + return $types[config.type]; + if (config.type instanceof Type) + return config.type; + return new Type(config.type); + } + + // array config: param name (param[]) overrides default settings. explicit config overrides param name. + function getArrayMode() { + var arrayDefaults = { array: (location === "search" ? "auto" : false) }; + var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {}; + return extend(arrayDefaults, arrayParamNomenclature, config).array; + } + + /** + * returns false, true, or the squash value to indicate the "default parameter url squash policy". + */ + function getSquashPolicy(config, isOptional) { + var squash = config.squash; + if (!isOptional || squash === false) return false; + if (!isDefined(squash) || squash == null) return defaultSquashPolicy; + if (squash === true || isString(squash)) return squash; + throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string"); + } + + function getReplace(config, arrayMode, isOptional, squash) { + var replace, configuredKeys, defaultPolicy = [ + { from: "", to: (isOptional || arrayMode ? undefined : "") }, + { from: null, to: (isOptional || arrayMode ? undefined : "") } + ]; + replace = isArray(config.replace) ? config.replace : []; + if (isString(squash)) + replace.push({ from: squash, to: undefined }); + configuredKeys = map(replace, function(item) { return item.from; } ); + return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace); + } + + /** + * [Internal] Get the default value of a parameter, which may be an injectable function. + */ + function $$getDefaultValue() { + if (!injector) throw new Error("Injectable functions cannot be called at configuration time"); + var defaultValue = injector.invoke(config.$$fn); + if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue)) + throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")"); + return defaultValue; + } + + /** + * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the + * default value, which may be the result of an injectable function. + */ + function $value(value) { + function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; } + function $replace(value) { + var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; }); + return replacement.length ? replacement[0] : value; + } + value = $replace(value); + return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value); + } + + function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; } + + extend(this, { + id: id, + type: type, + location: location, + array: arrayMode, + squash: squash, + replace: replace, + isOptional: isOptional, + value: $value, + dynamic: undefined, + config: config, + toString: toString + }); + }; + + function ParamSet(params) { + extend(this, params || {}); + } + + ParamSet.prototype = { + $$new: function() { + return inherit(this, extend(new ParamSet(), { $$parent: this})); + }, + $$keys: function () { + var keys = [], chain = [], parent = this, + ignore = objectKeys(ParamSet.prototype); + while (parent) { chain.push(parent); parent = parent.$$parent; } + chain.reverse(); + forEach(chain, function(paramset) { + forEach(objectKeys(paramset), function(key) { + if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key); + }); + }); + return keys; + }, + $$values: function(paramValues) { + var values = {}, self = this; + forEach(self.$$keys(), function(key) { + values[key] = self[key].value(paramValues && paramValues[key]); + }); + return values; + }, + $$equals: function(paramValues1, paramValues2) { + var equal = true, self = this; + forEach(self.$$keys(), function(key) { + var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key]; + if (!self[key].type.equals(left, right)) equal = false; + }); + return equal; + }, + $$validates: function $$validate(paramValues) { + var keys = this.$$keys(), i, param, rawVal, normalized, encoded; + for (i = 0; i < keys.length; i++) { + param = this[keys[i]]; + rawVal = paramValues[keys[i]]; + if ((rawVal === undefined || rawVal === null) && param.isOptional) + break; // There was no parameter value, but the param is optional + normalized = param.type.$normalize(rawVal); + if (!param.type.is(normalized)) + return false; // The value was not of the correct Type, and could not be decoded to the correct Type + encoded = param.type.encode(normalized); + if (angular.isString(encoded) && !param.type.pattern.exec(encoded)) + return false; // The value was of the correct type, but when encoded, did not match the Type's regexp + } + return true; + }, + $$parent: undefined + }; + + this.ParamSet = ParamSet; +} + +// Register as a provider so it's available to other providers +angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory); +angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]); + +/** + * @ngdoc object + * @name ui.router.router.$urlRouterProvider + * + * @requires ui.router.util.$urlMatcherFactoryProvider + * @requires $locationProvider + * + * @description + * `$urlRouterProvider` has the responsibility of watching `$location`. + * When `$location` changes it runs through a list of rules one by one until a + * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify + * a url in a state configuration. All urls are compiled into a UrlMatcher object. + * + * There are several methods on `$urlRouterProvider` that make it useful to use directly + * in your module config. + */ +$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider']; +function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) { + var rules = [], otherwise = null, interceptDeferred = false, listener; + + // Returns a string that is a prefix of all strings matching the RegExp + function regExpPrefix(re) { + var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source); + return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : ''; + } + + // Interpolates matched values into a String.replace()-style pattern + function interpolate(pattern, match) { + return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) { + return match[what === '$' ? 0 : Number(what)]; + }); + } + + /** + * @ngdoc function + * @name ui.router.router.$urlRouterProvider#rule + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Defines rules that are used by `$urlRouterProvider` to find matches for + * specific URLs. + * + * @example + *
+   * var app = angular.module('app', ['ui.router.router']);
+   *
+   * app.config(function ($urlRouterProvider) {
+   *   // Here's an example of how you might allow case insensitive urls
+   *   $urlRouterProvider.rule(function ($injector, $location) {
+   *     var path = $location.path(),
+   *         normalized = path.toLowerCase();
+   *
+   *     if (path !== normalized) {
+   *       return normalized;
+   *     }
+   *   });
+   * });
+   * 
+ * + * @param {function} rule Handler function that takes `$injector` and `$location` + * services as arguments. You can use them to return a valid path as a string. + * + * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance + */ + this.rule = function (rule) { + if (!isFunction(rule)) throw new Error("'rule' must be a function"); + rules.push(rule); + return this; + }; + + /** + * @ngdoc object + * @name ui.router.router.$urlRouterProvider#otherwise + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Defines a path that is used when an invalid route is requested. + * + * @example + *
+   * var app = angular.module('app', ['ui.router.router']);
+   *
+   * app.config(function ($urlRouterProvider) {
+   *   // if the path doesn't match any of the urls you configured
+   *   // otherwise will take care of routing the user to the
+   *   // specified url
+   *   $urlRouterProvider.otherwise('/index');
+   *
+   *   // Example of using function rule as param
+   *   $urlRouterProvider.otherwise(function ($injector, $location) {
+   *     return '/a/valid/url';
+   *   });
+   * });
+   * 
+ * + * @param {string|function} rule The url path you want to redirect to or a function + * rule that returns the url path. The function version is passed two params: + * `$injector` and `$location` services, and must return a url string. + * + * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance + */ + this.otherwise = function (rule) { + if (isString(rule)) { + var redirect = rule; + rule = function () { return redirect; }; + } + else if (!isFunction(rule)) throw new Error("'rule' must be a function"); + otherwise = rule; + return this; + }; + + + function handleIfMatch($injector, handler, match) { + if (!match) return false; + var result = $injector.invoke(handler, handler, { $match: match }); + return isDefined(result) ? result : true; + } + + /** + * @ngdoc function + * @name ui.router.router.$urlRouterProvider#when + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Registers a handler for a given url matching. + * + * If the handler is a string, it is + * treated as a redirect, and is interpolated according to the syntax of match + * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise). + * + * If the handler is a function, it is injectable. It gets invoked if `$location` + * matches. You have the option of inject the match object as `$match`. + * + * The handler can return + * + * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` + * will continue trying to find another one that matches. + * - **string** which is treated as a redirect and passed to `$location.url()` + * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. + * + * @example + *
+   * var app = angular.module('app', ['ui.router.router']);
+   *
+   * app.config(function ($urlRouterProvider) {
+   *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
+   *     if ($state.$current.navigable !== state ||
+   *         !equalForKeys($match, $stateParams) {
+   *      $state.transitionTo(state, $match, false);
+   *     }
+   *   });
+   * });
+   * 
+ * + * @param {string|object} what The incoming path that you want to redirect. + * @param {string|function} handler The path you want to redirect your user to. + */ + this.when = function (what, handler) { + var redirect, handlerIsString = isString(handler); + if (isString(what)) what = $urlMatcherFactory.compile(what); + + if (!handlerIsString && !isFunction(handler) && !isArray(handler)) + throw new Error("invalid 'handler' in when()"); + + var strategies = { + matcher: function (what, handler) { + if (handlerIsString) { + redirect = $urlMatcherFactory.compile(handler); + handler = ['$match', function ($match) { return redirect.format($match); }]; + } + return extend(function ($injector, $location) { + return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); + }, { + prefix: isString(what.prefix) ? what.prefix : '' + }); + }, + regex: function (what, handler) { + if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); + + if (handlerIsString) { + redirect = handler; + handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; + } + return extend(function ($injector, $location) { + return handleIfMatch($injector, handler, what.exec($location.path())); + }, { + prefix: regExpPrefix(what) + }); + } + }; + + var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; + + for (var n in check) { + if (check[n]) return this.rule(strategies[n](what, handler)); + } + + throw new Error("invalid 'what' in when()"); + }; + + /** + * @ngdoc function + * @name ui.router.router.$urlRouterProvider#deferIntercept + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Disables (or enables) deferring location change interception. + * + * If you wish to customize the behavior of syncing the URL (for example, if you wish to + * defer a transition but maintain the current URL), call this method at configuration time. + * Then, at run time, call `$urlRouter.listen()` after you have configured your own + * `$locationChangeSuccess` event handler. + * + * @example + *
+   * var app = angular.module('app', ['ui.router.router']);
+   *
+   * app.config(function ($urlRouterProvider) {
+   *
+   *   // Prevent $urlRouter from automatically intercepting URL changes;
+   *   // this allows you to configure custom behavior in between
+   *   // location changes and route synchronization:
+   *   $urlRouterProvider.deferIntercept();
+   *
+   * }).run(function ($rootScope, $urlRouter, UserService) {
+   *
+   *   $rootScope.$on('$locationChangeSuccess', function(e) {
+   *     // UserService is an example service for managing user state
+   *     if (UserService.isLoggedIn()) return;
+   *
+   *     // Prevent $urlRouter's default handler from firing
+   *     e.preventDefault();
+   *
+   *     UserService.handleLogin().then(function() {
+   *       // Once the user has logged in, sync the current URL
+   *       // to the router:
+   *       $urlRouter.sync();
+   *     });
+   *   });
+   *
+   *   // Configures $urlRouter's listener *after* your custom listener
+   *   $urlRouter.listen();
+   * });
+   * 
+ * + * @param {boolean} defer Indicates whether to defer location change interception. Passing + no parameter is equivalent to `true`. + */ + this.deferIntercept = function (defer) { + if (defer === undefined) defer = true; + interceptDeferred = defer; + }; + + /** + * @ngdoc object + * @name ui.router.router.$urlRouter + * + * @requires $location + * @requires $rootScope + * @requires $injector + * @requires $browser + * + * @description + * + */ + this.$get = $get; + $get.$inject = ['$location', '$rootScope', '$injector', '$browser', '$sniffer']; + function $get( $location, $rootScope, $injector, $browser, $sniffer) { + + var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl; + + function appendBasePath(url, isHtml5, absolute) { + if (baseHref === '/') return url; + if (isHtml5) return baseHref.slice(0, -1) + url; + if (absolute) return baseHref.slice(1) + url; + return url; + } + + // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree + function update(evt) { + if (evt && evt.defaultPrevented) return; + var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl; + lastPushedUrl = undefined; + // TODO: Re-implement this in 1.0 for https://github.com/angular-ui/ui-router/issues/1573 + //if (ignoreUpdate) return true; + + function check(rule) { + var handled = rule($injector, $location); + + if (!handled) return false; + if (isString(handled)) $location.replace().url(handled); + return true; + } + var n = rules.length, i; + + for (i = 0; i < n; i++) { + if (check(rules[i])) return; + } + // always check otherwise last to allow dynamic updates to the set of rules + if (otherwise) check(otherwise); + } + + function listen() { + listener = listener || $rootScope.$on('$locationChangeSuccess', update); + return listener; + } + + if (!interceptDeferred) listen(); + + return { + /** + * @ngdoc function + * @name ui.router.router.$urlRouter#sync + * @methodOf ui.router.router.$urlRouter + * + * @description + * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`. + * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event, + * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed + * with the transition by calling `$urlRouter.sync()`. + * + * @example + *
+       * angular.module('app', ['ui.router'])
+       *   .run(function($rootScope, $urlRouter) {
+       *     $rootScope.$on('$locationChangeSuccess', function(evt) {
+       *       // Halt state change from even starting
+       *       evt.preventDefault();
+       *       // Perform custom logic
+       *       var meetsRequirement = ...
+       *       // Continue with the update and state transition if logic allows
+       *       if (meetsRequirement) $urlRouter.sync();
+       *     });
+       * });
+       * 
+ */ + sync: function() { + update(); + }, + + listen: function() { + return listen(); + }, + + update: function(read) { + if (read) { + location = $location.url(); + return; + } + if ($location.url() === location) return; + + $location.url(location); + $location.replace(); + }, + + push: function(urlMatcher, params, options) { + var url = urlMatcher.format(params || {}); + + // Handle the special hash param, if needed + if (url !== null && params && params['#']) { + url += '#' + params['#']; + } + + $location.url(url); + lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined; + if (options && options.replace) $location.replace(); + }, + + /** + * @ngdoc function + * @name ui.router.router.$urlRouter#href + * @methodOf ui.router.router.$urlRouter + * + * @description + * A URL generation method that returns the compiled URL for a given + * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters. + * + * @example + *
+       * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
+       *   person: "bob"
+       * });
+       * // $bob == "/about/bob";
+       * 
+ * + * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate. + * @param {object=} params An object of parameter values to fill the matcher's required parameters. + * @param {object=} options Options object. The options are: + * + * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". + * + * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher` + */ + href: function(urlMatcher, params, options) { + if (!urlMatcher.validates(params)) return null; + + var isHtml5 = $locationProvider.html5Mode(); + if (angular.isObject(isHtml5)) { + isHtml5 = isHtml5.enabled; + } + + isHtml5 = isHtml5 && $sniffer.history; + + var url = urlMatcher.format(params); + options = options || {}; + + if (!isHtml5 && url !== null) { + url = "#" + $locationProvider.hashPrefix() + url; + } + + // Handle special hash param, if needed + if (url !== null && params && params['#']) { + url += '#' + params['#']; + } + + url = appendBasePath(url, isHtml5, options.absolute); + + if (!options.absolute || !url) { + return url; + } + + var slash = (!isHtml5 && url ? '/' : ''), port = $location.port(); + port = (port === 80 || port === 443 ? '' : ':' + port); + + return [$location.protocol(), '://', $location.host(), port, slash, url].join(''); + } + }; + } +} + +angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider); + +/** + * @ngdoc object + * @name ui.router.state.$stateProvider + * + * @requires ui.router.router.$urlRouterProvider + * @requires ui.router.util.$urlMatcherFactoryProvider + * + * @description + * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely + * on state. + * + * A state corresponds to a "place" in the application in terms of the overall UI and + * navigation. A state describes (via the controller / template / view properties) what + * the UI looks like and does at that place. + * + * States often have things in common, and the primary way of factoring out these + * commonalities in this model is via the state hierarchy, i.e. parent/child states aka + * nested states. + * + * The `$stateProvider` provides interfaces to declare these states for your app. + */ +$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider']; +function $StateProvider( $urlRouterProvider, $urlMatcherFactory) { + + var root, states = {}, $state, queue = {}, abstractKey = 'abstract'; + + // Builds state properties from definition passed to registerState() + var stateBuilder = { + + // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined. + // state.children = []; + // if (parent) parent.children.push(state); + parent: function(state) { + if (isDefined(state.parent) && state.parent) return findState(state.parent); + // regex matches any valid composite state name + // would match "contact.list" but not "contacts" + var compositeName = /^(.+)\.[^.]+$/.exec(state.name); + return compositeName ? findState(compositeName[1]) : root; + }, + + // inherit 'data' from parent and override by own values (if any) + data: function(state) { + if (state.parent && state.parent.data) { + state.data = state.self.data = inherit(state.parent.data, state.data); + } + return state.data; + }, + + // Build a URLMatcher if necessary, either via a relative or absolute URL + url: function(state) { + var url = state.url, config = { params: state.params || {} }; + + if (isString(url)) { + if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config); + return (state.parent.navigable || root).url.concat(url, config); + } + + if (!url || $urlMatcherFactory.isMatcher(url)) return url; + throw new Error("Invalid url '" + url + "' in state '" + state + "'"); + }, + + // Keep track of the closest ancestor state that has a URL (i.e. is navigable) + navigable: function(state) { + return state.url ? state : (state.parent ? state.parent.navigable : null); + }, + + // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params + ownParams: function(state) { + var params = state.url && state.url.params || new $$UMFP.ParamSet(); + forEach(state.params || {}, function(config, id) { + if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config"); + }); + return params; + }, + + // Derive parameters for this state and ensure they're a super-set of parent's parameters + params: function(state) { + var ownParams = pick(state.ownParams, state.ownParams.$$keys()); + return state.parent && state.parent.params ? extend(state.parent.params.$$new(), ownParams) : new $$UMFP.ParamSet(); + }, + + // If there is no explicit multi-view configuration, make one up so we don't have + // to handle both cases in the view directive later. Note that having an explicit + // 'views' property will mean the default unnamed view properties are ignored. This + // is also a good time to resolve view names to absolute names, so everything is a + // straight lookup at link time. + views: function(state) { + var views = {}; + + forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) { + if (name.indexOf('@') < 0) name += '@' + state.parent.name; + views[name] = view; + }); + return views; + }, + + // Keep a full path from the root down to this state as this is needed for state activation. + path: function(state) { + return state.parent ? state.parent.path.concat(state) : []; // exclude root from path + }, + + // Speed up $state.contains() as it's used a lot + includes: function(state) { + var includes = state.parent ? extend({}, state.parent.includes) : {}; + includes[state.name] = true; + return includes; + }, + + $delegates: {} + }; + + function isRelative(stateName) { + return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; + } + + function findState(stateOrName, base) { + if (!stateOrName) return undefined; + + var isStr = isString(stateOrName), + name = isStr ? stateOrName : stateOrName.name, + path = isRelative(name); + + if (path) { + if (!base) throw new Error("No reference point given for path '" + name + "'"); + base = findState(base); + + var rel = name.split("."), i = 0, pathLength = rel.length, current = base; + + for (; i < pathLength; i++) { + if (rel[i] === "" && i === 0) { + current = base; + continue; + } + if (rel[i] === "^") { + if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'"); + current = current.parent; + continue; + } + break; + } + rel = rel.slice(i).join("."); + name = current.name + (current.name && rel ? "." : "") + rel; + } + var state = states[name]; + + if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { + return state; + } + return undefined; + } + + function queueState(parentName, state) { + if (!queue[parentName]) { + queue[parentName] = []; + } + queue[parentName].push(state); + } + + function flushQueuedChildren(parentName) { + var queued = queue[parentName] || []; + while(queued.length) { + registerState(queued.shift()); + } + } + + function registerState(state) { + // Wrap a new object around the state so we can store our private details easily. + state = inherit(state, { + self: state, + resolve: state.resolve || {}, + toString: function() { return this.name; } + }); + + var name = state.name; + if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name"); + if (states.hasOwnProperty(name)) throw new Error("State '" + name + "' is already defined"); + + // Get parent name + var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.')) + : (isString(state.parent)) ? state.parent + : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name + : ''; + + // If parent is not registered yet, add state to queue and register later + if (parentName && !states[parentName]) { + return queueState(parentName, state.self); + } + + for (var key in stateBuilder) { + if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]); + } + states[name] = state; + + // Register the state in the global state list and with $urlRouter if necessary. + if (!state[abstractKey] && state.url) { + $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { + if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { + $state.transitionTo(state, $match, { inherit: true, location: false }); + } + }]); + } + + // Register any queued children + flushQueuedChildren(name); + + return state; + } + + // Checks text to see if it looks like a glob. + function isGlob (text) { + return text.indexOf('*') > -1; + } + + // Returns true if glob matches current $state name. + function doesStateMatchGlob (glob) { + var globSegments = glob.split('.'), + segments = $state.$current.name.split('.'); + + //match single stars + for (var i = 0, l = globSegments.length; i < l; i++) { + if (globSegments[i] === '*') { + segments[i] = '*'; + } + } + + //match greedy starts + if (globSegments[0] === '**') { + segments = segments.slice(indexOf(segments, globSegments[1])); + segments.unshift('**'); + } + //match greedy ends + if (globSegments[globSegments.length - 1] === '**') { + segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE); + segments.push('**'); + } + + if (globSegments.length != segments.length) { + return false; + } + + return segments.join('') === globSegments.join(''); + } + + + // Implicit root state that is always active + root = registerState({ + name: '', + url: '^', + views: null, + 'abstract': true + }); + root.navigable = null; + + + /** + * @ngdoc function + * @name ui.router.state.$stateProvider#decorator + * @methodOf ui.router.state.$stateProvider + * + * @description + * Allows you to extend (carefully) or override (at your own peril) the + * `stateBuilder` object used internally by `$stateProvider`. This can be used + * to add custom functionality to ui-router, for example inferring templateUrl + * based on the state name. + * + * When passing only a name, it returns the current (original or decorated) builder + * function that matches `name`. + * + * The builder functions that can be decorated are listed below. Though not all + * necessarily have a good use case for decoration, that is up to you to decide. + * + * In addition, users can attach custom decorators, which will generate new + * properties within the state's internal definition. There is currently no clear + * use-case for this beyond accessing internal states (i.e. $state.$current), + * however, expect this to become increasingly relevant as we introduce additional + * meta-programming features. + * + * **Warning**: Decorators should not be interdependent because the order of + * execution of the builder functions in non-deterministic. Builder functions + * should only be dependent on the state definition object and super function. + * + * + * Existing builder functions and current return values: + * + * - **parent** `{object}` - returns the parent state object. + * - **data** `{object}` - returns state data, including any inherited data that is not + * overridden by own values (if any). + * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher} + * or `null`. + * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is + * navigable). + * - **params** `{object}` - returns an array of state params that are ensured to + * be a super-set of parent's params. + * - **views** `{object}` - returns a views object where each key is an absolute view + * name (i.e. "viewName@stateName") and each value is the config object + * (template, controller) for the view. Even when you don't use the views object + * explicitly on a state config, one is still created for you internally. + * So by decorating this builder function you have access to decorating template + * and controller properties. + * - **ownParams** `{object}` - returns an array of params that belong to the state, + * not including any params defined by ancestor states. + * - **path** `{string}` - returns the full path from the root down to this state. + * Needed for state activation. + * - **includes** `{object}` - returns an object that includes every state that + * would pass a `$state.includes()` test. + * + * @example + *
+   * // Override the internal 'views' builder with a function that takes the state
+   * // definition, and a reference to the internal function being overridden:
+   * $stateProvider.decorator('views', function (state, parent) {
+   *   var result = {},
+   *       views = parent(state);
+   *
+   *   angular.forEach(views, function (config, name) {
+   *     var autoName = (state.name + '.' + name).replace('.', '/');
+   *     config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
+   *     result[name] = config;
+   *   });
+   *   return result;
+   * });
+   *
+   * $stateProvider.state('home', {
+   *   views: {
+   *     'contact.list': { controller: 'ListController' },
+   *     'contact.item': { controller: 'ItemController' }
+   *   }
+   * });
+   *
+   * // ...
+   *
+   * $state.go('home');
+   * // Auto-populates list and item views with /partials/home/contact/list.html,
+   * // and /partials/home/contact/item.html, respectively.
+   * 
+ * + * @param {string} name The name of the builder function to decorate. + * @param {object} func A function that is responsible for decorating the original + * builder function. The function receives two parameters: + * + * - `{object}` - state - The state config object. + * - `{object}` - super - The original builder function. + * + * @return {object} $stateProvider - $stateProvider instance + */ + this.decorator = decorator; + function decorator(name, func) { + /*jshint validthis: true */ + if (isString(name) && !isDefined(func)) { + return stateBuilder[name]; + } + if (!isFunction(func) || !isString(name)) { + return this; + } + if (stateBuilder[name] && !stateBuilder.$delegates[name]) { + stateBuilder.$delegates[name] = stateBuilder[name]; + } + stateBuilder[name] = func; + return this; + } + + /** + * @ngdoc function + * @name ui.router.state.$stateProvider#state + * @methodOf ui.router.state.$stateProvider + * + * @description + * Registers a state configuration under a given state name. The stateConfig object + * has the following acceptable properties. + * + * @param {string} name A unique state name, e.g. "home", "about", "contacts". + * To create a parent/child state use a dot, e.g. "about.sales", "home.newest". + * @param {object} stateConfig State configuration object. + * @param {string|function=} stateConfig.template + * + * html template as a string or a function that returns + * an html template as a string which should be used by the uiView directives. This property + * takes precedence over templateUrl. + * + * If `template` is a function, it will be called with the following parameters: + * + * - {array.<object>} - state parameters extracted from the current $location.path() by + * applying the current state + * + *
template:
+   *   "

inline template definition

" + + * "
"
+ *
template: function(params) {
+   *       return "

generated template

"; }
+ * + * + * @param {string|function=} stateConfig.templateUrl + * + * + * path or function that returns a path to an html + * template that should be used by uiView. + * + * If `templateUrl` is a function, it will be called with the following parameters: + * + * - {array.<object>} - state parameters extracted from the current $location.path() by + * applying the current state + * + *
templateUrl: "home.html"
+ *
templateUrl: function(params) {
+   *     return myTemplates[params.pageId]; }
+ * + * @param {function=} stateConfig.templateProvider + * + * Provider function that returns HTML content string. + *
 templateProvider:
+   *       function(MyTemplateService, params) {
+   *         return MyTemplateService.getTemplate(params.pageId);
+   *       }
+ * + * @param {string|function=} stateConfig.controller + * + * + * Controller fn that should be associated with newly + * related scope or the name of a registered controller if passed as a string. + * Optionally, the ControllerAs may be declared here. + *
controller: "MyRegisteredController"
+ *
controller:
+   *     "MyRegisteredController as fooCtrl"}
+ *
controller: function($scope, MyService) {
+   *     $scope.data = MyService.getData(); }
+ * + * @param {function=} stateConfig.controllerProvider + * + * + * Injectable provider function that returns the actual controller or string. + *
controllerProvider:
+   *   function(MyResolveData) {
+   *     if (MyResolveData.foo)
+   *       return "FooCtrl"
+   *     else if (MyResolveData.bar)
+   *       return "BarCtrl";
+   *     else return function($scope) {
+   *       $scope.baz = "Qux";
+   *     }
+   *   }
+ * + * @param {string=} stateConfig.controllerAs + * + * + * A controller alias name. If present the controller will be + * published to scope under the controllerAs name. + *
controllerAs: "myCtrl"
+ * + * @param {string|object=} stateConfig.parent + * + * Optionally specifies the parent state of this state. + * + *
parent: 'parentState'
+ *
parent: parentState // JS variable
+ * + * @param {object=} stateConfig.resolve + * + * + * An optional map<string, function> 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 before the controller is instantiated. + * If all the promises are resolved successfully, the $stateChangeSuccess event is fired + * and the values of the resolved promises are injected into any controllers that reference them. + * If any of the promises are rejected the $stateChangeError event is fired. + * + * The map object is: + * + * - key - {string}: name of dependency to be injected into controller + * - factory - {string|function}: If string then it is alias for service. Otherwise if function, + * it is injected and return value it treated as dependency. If result is a promise, it is + * resolved before its value is injected into controller. + * + *
resolve: {
+   *     myResolve1:
+   *       function($http, $stateParams) {
+   *         return $http.get("/api/foos/"+stateParams.fooID);
+   *       }
+   *     }
+ * + * @param {string=} stateConfig.url + * + * + * A url fragment with optional parameters. When a state is navigated or + * transitioned to, the `$stateParams` service will be populated with any + * parameters that were passed. + * + * (See {@link ui.router.util.type:UrlMatcher UrlMatcher} `UrlMatcher`} for + * more details on acceptable patterns ) + * + * examples: + *
url: "/home"
+   * url: "/users/:userid"
+   * url: "/books/{bookid:[a-zA-Z_-]}"
+   * url: "/books/{categoryid:int}"
+   * url: "/books/{publishername:string}/{categoryid:int}"
+   * url: "/messages?before&after"
+   * url: "/messages?{before:date}&{after:date}"
+   * url: "/messages/:mailboxid?{before:date}&{after:date}"
+   * 
+ * + * @param {object=} stateConfig.views + * + * an optional map<string, object> which defined multiple views, or targets views + * manually/explicitly. + * + * Examples: + * + * Targets three named `ui-view`s in the parent state's template + *
views: {
+   *     header: {
+   *       controller: "headerCtrl",
+   *       templateUrl: "header.html"
+   *     }, body: {
+   *       controller: "bodyCtrl",
+   *       templateUrl: "body.html"
+   *     }, footer: {
+   *       controller: "footCtrl",
+   *       templateUrl: "footer.html"
+   *     }
+   *   }
+ * + * Targets named `ui-view="header"` from grandparent state 'top''s template, and named `ui-view="body" from parent state's template. + *
views: {
+   *     'header@top': {
+   *       controller: "msgHeaderCtrl",
+   *       templateUrl: "msgHeader.html"
+   *     }, 'body': {
+   *       controller: "messagesCtrl",
+   *       templateUrl: "messages.html"
+   *     }
+   *   }
+ * + * @param {boolean=} [stateConfig.abstract=false] + * + * An abstract state will never be directly activated, + * but can provide inherited properties to its common children states. + *
abstract: true
+ * + * @param {function=} stateConfig.onEnter + * + * + * Callback function for when a state is entered. Good way + * to trigger an action or dispatch an event, such as opening a dialog. + * If minifying your scripts, make sure to explicitly annotate this function, + * because it won't be automatically annotated by your build tools. + * + *
onEnter: function(MyService, $stateParams) {
+   *     MyService.foo($stateParams.myParam);
+   * }
+ * + * @param {function=} stateConfig.onExit + * + * + * Callback function for when a state is exited. Good way to + * trigger an action or dispatch an event, such as opening a dialog. + * If minifying your scripts, make sure to explicitly annotate this function, + * because it won't be automatically annotated by your build tools. + * + *
onExit: function(MyService, $stateParams) {
+   *     MyService.cleanup($stateParams.myParam);
+   * }
+ * + * @param {boolean=} [stateConfig.reloadOnSearch=true] + * + * + * If `false`, will not retrigger the same state + * just because a search/query parameter has changed (via $location.search() or $location.hash()). + * Useful for when you'd like to modify $location.search() without triggering a reload. + *
reloadOnSearch: false
+ * + * @param {object=} stateConfig.data + * + * + * Arbitrary data object, useful for custom configuration. The parent state's `data` is + * prototypally inherited. In other words, adding a data property to a state adds it to + * the entire subtree via prototypal inheritance. + * + *
data: {
+   *     requiredRole: 'foo'
+   * } 
+ * + * @param {object=} stateConfig.params + * + * + * A map which optionally configures parameters declared in the `url`, or + * defines additional non-url parameters. For each parameter being + * configured, add a configuration object keyed to the name of the parameter. + * + * Each parameter configuration object may contain the following properties: + * + * - ** value ** - {object|function=}: specifies the default value for this + * parameter. This implicitly sets this parameter as optional. + * + * When UI-Router routes to a state and no value is + * specified for this parameter in the URL or transition, the + * default value will be used instead. If `value` is a function, + * it will be injected and invoked, and the return value used. + * + * *Note*: `undefined` is treated as "no default value" while `null` + * is treated as "the default value is `null`". + * + * *Shorthand*: If you only need to configure the default value of the + * parameter, you may use a shorthand syntax. In the **`params`** + * map, instead mapping the param name to a full parameter configuration + * object, simply set map it to the default parameter value, e.g.: + * + *
// define a parameter's default value
+   * params: {
+   *     param1: { value: "defaultValue" }
+   * }
+   * // shorthand default values
+   * params: {
+   *     param1: "defaultValue",
+   *     param2: "param2Default"
+   * }
+ * + * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be + * treated as an array of values. If you specified a Type, the value will be + * treated as an array of the specified Type. Note: query parameter values + * default to a special `"auto"` mode. + * + * For query parameters in `"auto"` mode, if multiple values for a single parameter + * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values + * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if + * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single + * value (e.g.: `{ foo: '1' }`). + * + *
params: {
+   *     param1: { array: true }
+   * }
+ * + * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when + * the current parameter value is the same as the default value. If `squash` is not set, it uses the + * configured default squash policy. + * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`}) + * + * There are three squash settings: + * + * - false: The parameter's default value is not squashed. It is encoded and included in the URL + * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed + * by slashes in the state's `url` declaration, then one of those slashes are omitted. + * This can allow for cleaner looking URLs. + * - `""`: The parameter's default value is replaced with an arbitrary placeholder of your choice. + * + *
params: {
+   *     param1: {
+   *       value: "defaultId",
+   *       squash: true
+   * } }
+   * // squash "defaultValue" to "~"
+   * params: {
+   *     param1: {
+   *       value: "defaultValue",
+   *       squash: "~"
+   * } }
+   * 
+ * + * + * @example + *
+   * // Some state name examples
+   *
+   * // stateName can be a single top-level name (must be unique).
+   * $stateProvider.state("home", {});
+   *
+   * // Or it can be a nested state name. This state is a child of the
+   * // above "home" state.
+   * $stateProvider.state("home.newest", {});
+   *
+   * // Nest states as deeply as needed.
+   * $stateProvider.state("home.newest.abc.xyz.inception", {});
+   *
+   * // state() returns $stateProvider, so you can chain state declarations.
+   * $stateProvider
+   *   .state("home", {})
+   *   .state("about", {})
+   *   .state("contacts", {});
+   * 
+ * + */ + this.state = state; + function state(name, definition) { + /*jshint validthis: true */ + if (isObject(name)) definition = name; + else definition.name = name; + registerState(definition); + return this; + } + + /** + * @ngdoc object + * @name ui.router.state.$state + * + * @requires $rootScope + * @requires $q + * @requires ui.router.state.$view + * @requires $injector + * @requires ui.router.util.$resolve + * @requires ui.router.state.$stateParams + * @requires ui.router.router.$urlRouter + * + * @property {object} params A param object, e.g. {sectionId: section.id)}, that + * you'd like to test against the current active state. + * @property {object} current A reference to the state's config object. However + * you passed it in. Useful for accessing custom data. + * @property {object} transition Currently pending transition. A promise that'll + * resolve or reject. + * + * @description + * `$state` service is responsible for representing states as well as transitioning + * between them. It also provides interfaces to ask for current state or even states + * you're coming from. + */ + this.$get = $get; + $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory']; + function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) { + + var TransitionSuperseded = $q.reject(new Error('transition superseded')); + var TransitionPrevented = $q.reject(new Error('transition prevented')); + var TransitionAborted = $q.reject(new Error('transition aborted')); + var TransitionFailed = $q.reject(new Error('transition failed')); + + // Handles the case where a state which is the target of a transition is not found, and the user + // can optionally retry or defer the transition + function handleRedirect(redirect, state, params, options) { + /** + * @ngdoc event + * @name ui.router.state.$state#$stateNotFound + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when a requested state **cannot be found** using the provided state name during transition. + * The event is broadcast allowing any handlers a single chance to deal with the error (usually by + * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler, + * you can see its three properties in the example. You can use `event.preventDefault()` to abort the + * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value. + * + * @param {Object} event Event object. + * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties. + * @param {State} fromState Current state object. + * @param {Object} fromParams Current state params. + * + * @example + * + *
+       * // somewhere, assume lazy.state has not been defined
+       * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
+       *
+       * // somewhere else
+       * $scope.$on('$stateNotFound',
+       * function(event, unfoundState, fromState, fromParams){
+       *     console.log(unfoundState.to); // "lazy.state"
+       *     console.log(unfoundState.toParams); // {a:1, b:2}
+       *     console.log(unfoundState.options); // {inherit:false} + default options
+       * })
+       * 
+ */ + var evt = $rootScope.$broadcast('$stateNotFound', redirect, state, params); + + if (evt.defaultPrevented) { + $urlRouter.update(); + return TransitionAborted; + } + + if (!evt.retry) { + return null; + } + + // Allow the handler to return a promise to defer state lookup retry + if (options.$retry) { + $urlRouter.update(); + return TransitionFailed; + } + var retryTransition = $state.transition = $q.when(evt.retry); + + retryTransition.then(function() { + if (retryTransition !== $state.transition) return TransitionSuperseded; + redirect.options.$retry = true; + return $state.transitionTo(redirect.to, redirect.toParams, redirect.options); + }, function() { + return TransitionAborted; + }); + $urlRouter.update(); + + return retryTransition; + } + + root.locals = { resolve: null, globals: { $stateParams: {} } }; + + $state = { + params: {}, + current: root.self, + $current: root, + transition: null + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#reload + * @methodOf ui.router.state.$state + * + * @description + * A method that force reloads the current state. All resolves are re-resolved, + * controllers reinstantiated, and events re-fired. + * + * @example + *
+     * var app angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.reload = function(){
+     *     $state.reload();
+     *   }
+     * });
+     * 
+ * + * `reload()` is just an alias for: + *
+     * $state.transitionTo($state.current, $stateParams, { 
+     *   reload: true, inherit: false, notify: true
+     * });
+     * 
+ * + * @param {string=|object=} state - A state name or a state object, which is the root of the resolves to be re-resolved. + * @example + *
+     * //assuming app application consists of 3 states: 'contacts', 'contacts.detail', 'contacts.detail.item' 
+     * //and current state is 'contacts.detail.item'
+     * var app angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.reload = function(){
+     *     //will reload 'contact.detail' and 'contact.detail.item' states
+     *     $state.reload('contact.detail');
+     *   }
+     * });
+     * 
+ * + * `reload()` is just an alias for: + *
+     * $state.transitionTo($state.current, $stateParams, { 
+     *   reload: true, inherit: false, notify: true
+     * });
+     * 
+ + * @returns {promise} A promise representing the state of the new transition. See + * {@link ui.router.state.$state#methods_go $state.go}. + */ + $state.reload = function reload(state) { + return $state.transitionTo($state.current, $stateParams, { reload: state || true, inherit: false, notify: true}); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#go + * @methodOf ui.router.state.$state + * + * @description + * Convenience method for transitioning to a new state. `$state.go` calls + * `$state.transitionTo` internally but automatically sets options to + * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. + * This allows you to easily use an absolute or relative to path and specify + * only the parameters you'd like to update (while letting unspecified parameters + * inherit from the currently active ancestor states). + * + * @example + *
+     * var app = angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.changeState = function () {
+     *     $state.go('contact.detail');
+     *   };
+     * });
+     * 
+ * + * + * @param {string} to Absolute state name or relative state path. Some examples: + * + * - `$state.go('contact.detail')` - will go to the `contact.detail` state + * - `$state.go('^')` - will go to a parent state + * - `$state.go('^.sibling')` - will go to a sibling state + * - `$state.go('.child.grandchild')` - will go to grandchild state + * + * @param {object=} params A map of the parameters that will be sent to the state, + * will populate $stateParams. Any parameters that are not specified will be inherited from currently + * defined parameters. Only parameters specified in the state definition can be overridden, new + * parameters will be ignored. This allows, for example, going to a sibling state that shares parameters + * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e. + * transitioning to a sibling will get you the parameters for all parents, transitioning to a child + * will get you all current parameters, etc. + * @param {object=} options Options object. The options are: + * + * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` + * will not. If string, must be `"replace"`, which will update url and also replace last history record. + * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. + * - **`reload`** (v0.2.5) - {boolean=false|string|object}, If `true` will force transition even if no state or params + * have changed. It will reload the resolves and views of the current state and parent states. + * If `reload` is a string (or state object), the state object is fetched (by name, or object reference); and \ + * the transition reloads the resolves and views for that matched state, and all its children states. + * + * @returns {promise} A promise representing the state of the new transition. + * + * Possible success values: + * + * - $state.current + * + *
Possible rejection values: + * + * - 'transition superseded' - when a newer transition has been started after this one + * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener + * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or + * when a `$stateNotFound` `event.retry` promise errors. + * - 'transition failed' - when a state has been unsuccessfully found after 2 tries. + * - *resolve error* - when an error has occurred with a `resolve` + * + */ + $state.go = function go(to, params, options) { + return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#transitionTo + * @methodOf ui.router.state.$state + * + * @description + * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} + * uses `transitionTo` internally. `$state.go` is recommended in most situations. + * + * @example + *
+     * var app = angular.module('app', ['ui.router']);
+     *
+     * app.controller('ctrl', function ($scope, $state) {
+     *   $scope.changeState = function () {
+     *     $state.transitionTo('contact.detail');
+     *   };
+     * });
+     * 
+ * + * @param {string} to State name. + * @param {object=} toParams A map of the parameters that will be sent to the state, + * will populate $stateParams. + * @param {object=} options Options object. The options are: + * + * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false` + * will not. If string, must be `"replace"`, which will update url and also replace last history record. + * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events. + * - **`reload`** (v0.2.5) - {boolean=false|string=|object=}, If `true` will force transition even if the state or params + * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd + * use this when you want to force a reload when *everything* is the same, including search params. + * if String, then will reload the state with the name given in reload, and any children. + * if Object, then a stateObj is expected, will reload the state found in stateObj, and any children. + * + * @returns {promise} A promise representing the state of the new transition. See + * {@link ui.router.state.$state#methods_go $state.go}. + */ + $state.transitionTo = function transitionTo(to, toParams, options) { + toParams = toParams || {}; + options = extend({ + location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false + }, options || {}); + + var from = $state.$current, fromParams = $state.params, fromPath = from.path; + var evt, toState = findState(to, options.relative); + + // Store the hash param for later (since it will be stripped out by various methods) + var hash = toParams['#']; + + if (!isDefined(toState)) { + var redirect = { to: to, toParams: toParams, options: options }; + var redirectResult = handleRedirect(redirect, from.self, fromParams, options); + + if (redirectResult) { + return redirectResult; + } + + // Always retry once if the $stateNotFound was not prevented + // (handles either redirect changed or state lazy-definition) + to = redirect.to; + toParams = redirect.toParams; + options = redirect.options; + toState = findState(to, options.relative); + + if (!isDefined(toState)) { + if (!options.relative) throw new Error("No such state '" + to + "'"); + throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'"); + } + } + if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'"); + if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState); + if (!toState.params.$$validates(toParams)) return TransitionFailed; + + toParams = toState.params.$$values(toParams); + to = toState; + + var toPath = to.path; + + // Starting from the root of the path, keep all levels that haven't changed + var keep = 0, state = toPath[keep], locals = root.locals, toLocals = []; + + if (!options.reload) { + while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) { + locals = toLocals[keep] = state.locals; + keep++; + state = toPath[keep]; + } + } else if (isString(options.reload) || isObject(options.reload)) { + if (isObject(options.reload) && !options.reload.name) { + throw new Error('Invalid reload state object'); + } + + var reloadState = options.reload === true ? fromPath[0] : findState(options.reload); + if (options.reload && !reloadState) { + throw new Error("No such reload state '" + (isString(options.reload) ? options.reload : options.reload.name) + "'"); + } + + while (state && state === fromPath[keep] && state !== reloadState) { + locals = toLocals[keep] = state.locals; + keep++; + state = toPath[keep]; + } + } + + // If we're going to the same state and all locals are kept, we've got nothing to do. + // But clear 'transition', as we still want to cancel any other pending transitions. + // TODO: We may not want to bump 'transition' if we're called from a location change + // that we've initiated ourselves, because we might accidentally abort a legitimate + // transition initiated from code? + if (shouldSkipReload(to, toParams, from, fromParams, locals, options)) { + if (hash) toParams['#'] = hash; + $state.params = toParams; + copy($state.params, $stateParams); + copy(filterByKeys(to.params.$$keys(), $stateParams), to.locals.globals.$stateParams); + if (options.location && to.navigable && to.navigable.url) { + $urlRouter.push(to.navigable.url, toParams, { + $$avoidResync: true, replace: options.location === 'replace' + }); + $urlRouter.update(true); + } + $state.transition = null; + return $q.when($state.current); + } + + // Filter parameters before we pass them to event handlers etc. + toParams = filterByKeys(to.params.$$keys(), toParams || {}); + + // Re-add the saved hash before we start returning things or broadcasting $stateChangeStart + if (hash) toParams['#'] = hash; + + // Broadcast start event and cancel the transition if requested + if (options.notify) { + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeStart + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when the state transition **begins**. You can use `event.preventDefault()` + * to prevent the transition from happening and then the transition promise will be + * rejected with a `'transition prevented'` value. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + * + * @example + * + *
+         * $rootScope.$on('$stateChangeStart',
+         * function(event, toState, toParams, fromState, fromParams){
+         *     event.preventDefault();
+         *     // transitionTo() promise will be rejected with
+         *     // a 'transition prevented' error
+         * })
+         * 
+ */ + if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams, options).defaultPrevented) { + $rootScope.$broadcast('$stateChangeCancel', to.self, toParams, from.self, fromParams); + //Don't update and resync url if there's been a new transition started. see issue #2238, #600 + if ($state.transition == null) $urlRouter.update(); + return TransitionPrevented; + } + } + + // Resolve locals for the remaining states, but don't update any global state just + // yet -- if anything fails to resolve the current state needs to remain untouched. + // We also set up an inheritance chain for the locals here. This allows the view directive + // to quickly look up the correct definition for each view in the current state. Even + // though we create the locals object itself outside resolveState(), it is initially + // empty and gets filled asynchronously. We need to keep track of the promise for the + // (fully resolved) current locals, and pass this down the chain. + var resolved = $q.when(locals); + + for (var l = keep; l < toPath.length; l++, state = toPath[l]) { + locals = toLocals[l] = inherit(locals); + resolved = resolveState(state, toParams, state === to, resolved, locals, options); + } + + // Once everything is resolved, we are ready to perform the actual transition + // and return a promise for the new state. We also keep track of what the + // current promise is, so that we can detect overlapping transitions and + // keep only the outcome of the last transition. + var transition = $state.transition = resolved.then(function () { + var l, entering, exiting; + + if ($state.transition !== transition) return TransitionSuperseded; + + // Exit 'from' states not kept + for (l = fromPath.length - 1; l >= keep; l--) { + exiting = fromPath[l]; + if (exiting.self.onExit) { + $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals); + } + exiting.locals = null; + } + + // Enter 'to' states not kept + for (l = keep; l < toPath.length; l++) { + entering = toPath[l]; + entering.locals = toLocals[l]; + if (entering.self.onEnter) { + $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals); + } + } + + // Run it again, to catch any transitions in callbacks + if ($state.transition !== transition) return TransitionSuperseded; + + // Update globals in $state + $state.$current = to; + $state.current = to.self; + $state.params = toParams; + copy($state.params, $stateParams); + $state.transition = null; + + if (options.location && to.navigable) { + $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, { + $$avoidResync: true, replace: options.location === 'replace' + }); + } + + if (options.notify) { + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeSuccess + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired once the state transition is **complete**. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + */ + $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams); + } + $urlRouter.update(true); + + return $state.current; + }, function (error) { + if ($state.transition !== transition) return TransitionSuperseded; + + $state.transition = null; + /** + * @ngdoc event + * @name ui.router.state.$state#$stateChangeError + * @eventOf ui.router.state.$state + * @eventType broadcast on root scope + * @description + * Fired when an **error occurs** during transition. It's important to note that if you + * have any errors in your resolve functions (javascript errors, non-existent services, etc) + * they will not throw traditionally. You must listen for this $stateChangeError event to + * catch **ALL** errors. + * + * @param {Object} event Event object. + * @param {State} toState The state being transitioned to. + * @param {Object} toParams The params supplied to the `toState`. + * @param {State} fromState The current state, pre-transition. + * @param {Object} fromParams The params supplied to the `fromState`. + * @param {Error} error The resolve error object. + */ + evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error); + + if (!evt.defaultPrevented) { + $urlRouter.update(); + } + + return $q.reject(error); + }); + + return transition; + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#is + * @methodOf ui.router.state.$state + * + * @description + * Similar to {@link ui.router.state.$state#methods_includes $state.includes}, + * but only checks for the full state name. If params is supplied then it will be + * tested for strict equality against the current active params object, so all params + * must match with none missing and no extras. + * + * @example + *
+     * $state.$current.name = 'contacts.details.item';
+     *
+     * // absolute name
+     * $state.is('contact.details.item'); // returns true
+     * $state.is(contactDetailItemStateObject); // returns true
+     *
+     * // relative name (. and ^), typically from a template
+     * // E.g. from the 'contacts.details' template
+     * 
Item
+ *
+ * + * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check. + * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like + * to test against the current active state. + * @param {object=} options An options object. The options are: + * + * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will + * test relative to `options.relative` state (or name). + * + * @returns {boolean} Returns true if it is the state. + */ + $state.is = function is(stateOrName, params, options) { + options = extend({ relative: $state.$current }, options || {}); + var state = findState(stateOrName, options.relative); + + if (!isDefined(state)) { return undefined; } + if ($state.$current !== state) { return false; } + return params ? equalForKeys(state.params.$$values(params), $stateParams) : true; + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#includes + * @methodOf ui.router.state.$state + * + * @description + * A method to determine if the current active state is equal to or is the child of the + * state stateName. If any params are passed then they will be tested for a match as well. + * Not all the parameters need to be passed, just the ones you'd like to test for equality. + * + * @example + * Partial and relative names + *
+     * $state.$current.name = 'contacts.details.item';
+     *
+     * // Using partial names
+     * $state.includes("contacts"); // returns true
+     * $state.includes("contacts.details"); // returns true
+     * $state.includes("contacts.details.item"); // returns true
+     * $state.includes("contacts.list"); // returns false
+     * $state.includes("about"); // returns false
+     *
+     * // Using relative names (. and ^), typically from a template
+     * // E.g. from the 'contacts.details' template
+     * 
Item
+ *
+ * + * Basic globbing patterns + *
+     * $state.$current.name = 'contacts.details.item.url';
+     *
+     * $state.includes("*.details.*.*"); // returns true
+     * $state.includes("*.details.**"); // returns true
+     * $state.includes("**.item.**"); // returns true
+     * $state.includes("*.details.item.url"); // returns true
+     * $state.includes("*.details.*.url"); // returns true
+     * $state.includes("*.details.*"); // returns false
+     * $state.includes("item.**"); // returns false
+     * 
+ * + * @param {string} stateOrName A partial name, relative name, or glob pattern + * to be searched for within the current state name. + * @param {object=} params A param object, e.g. `{sectionId: section.id}`, + * that you'd like to test against the current active state. + * @param {object=} options An options object. The options are: + * + * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set, + * .includes will test relative to `options.relative` state (or name). + * + * @returns {boolean} Returns true if it does include the state + */ + $state.includes = function includes(stateOrName, params, options) { + options = extend({ relative: $state.$current }, options || {}); + if (isString(stateOrName) && isGlob(stateOrName)) { + if (!doesStateMatchGlob(stateOrName)) { + return false; + } + stateOrName = $state.$current.name; + } + + var state = findState(stateOrName, options.relative); + if (!isDefined(state)) { return undefined; } + if (!isDefined($state.$current.includes[state.name])) { return false; } + return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true; + }; + + + /** + * @ngdoc function + * @name ui.router.state.$state#href + * @methodOf ui.router.state.$state + * + * @description + * A url generation method that returns the compiled url for the given state populated with the given params. + * + * @example + *
+     * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
+     * 
+ * + * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. + * @param {object=} params An object of parameter values to fill the state's required parameters. + * @param {object=} options Options object. The options are: + * + * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the + * first parameter, then the constructed href url will be built from the first navigable ancestor (aka + * ancestor with a valid url). + * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url. + * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'), + * defines which state to be relative from. + * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl". + * + * @returns {string} compiled state url + */ + $state.href = function href(stateOrName, params, options) { + options = extend({ + lossy: true, + inherit: true, + absolute: false, + relative: $state.$current + }, options || {}); + + var state = findState(stateOrName, options.relative); + + if (!isDefined(state)) return null; + if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state); + + var nav = (state && options.lossy) ? state.navigable : state; + + if (!nav || nav.url === undefined || nav.url === null) { + return null; + } + return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys().concat('#'), params || {}), { + absolute: options.absolute + }); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#get + * @methodOf ui.router.state.$state + * + * @description + * Returns the state configuration object for any specific state or all states. + * + * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for + * the requested state. If not provided, returns an array of ALL state configs. + * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context. + * @returns {Object|Array} State configuration object or array of all objects. + */ + $state.get = function (stateOrName, context) { + if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; }); + var state = findState(stateOrName, context || $state.$current); + return (state && state.self) ? state.self : null; + }; + + function resolveState(state, params, paramsAreFiltered, inherited, dst, options) { + // Make a restricted $stateParams with only the parameters that apply to this state if + // necessary. In addition to being available to the controller and onEnter/onExit callbacks, + // we also need $stateParams to be available for any $injector calls we make during the + // dependency resolution process. + var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params); + var locals = { $stateParams: $stateParams }; + + // Resolve 'global' dependencies for the state, i.e. those not specific to a view. + // We're also including $stateParams in this; that way the parameters are restricted + // to the set that should be visible to the state, and are independent of when we update + // the global $state and $stateParams values. + dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state); + var promises = [dst.resolve.then(function (globals) { + dst.globals = globals; + })]; + if (inherited) promises.push(inherited); + + function resolveViews() { + var viewsPromises = []; + + // Resolve template and dependencies for all views. + forEach(state.views, function (view, name) { + var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); + injectables.$template = [ function () { + return $view.load(name, { view: view, locals: dst.globals, params: $stateParams, notify: options.notify }) || ''; + }]; + + viewsPromises.push($resolve.resolve(injectables, dst.globals, dst.resolve, state).then(function (result) { + // References to the controller (only instantiated at link time) + if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { + var injectLocals = angular.extend({}, injectables, dst.globals); + result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); + } else { + result.$$controller = view.controller; + } + // Provide access to the state itself for internal use + result.$$state = state; + result.$$controllerAs = view.controllerAs; + dst[name] = result; + })); + }); + + return $q.all(viewsPromises).then(function(){ + return dst.globals; + }); + } + + // Wait for all the promises and then return the activation object + return $q.all(promises).then(resolveViews).then(function (values) { + return dst; + }); + } + + return $state; + } + + function shouldSkipReload(to, toParams, from, fromParams, locals, options) { + // Return true if there are no differences in non-search (path/object) params, false if there are differences + function nonSearchParamsEqual(fromAndToState, fromParams, toParams) { + // Identify whether all the parameters that differ between `fromParams` and `toParams` were search params. + function notSearchParam(key) { + return fromAndToState.params[key].location != "search"; + } + var nonQueryParamKeys = fromAndToState.params.$$keys().filter(notSearchParam); + var nonQueryParams = pick.apply({}, [fromAndToState.params].concat(nonQueryParamKeys)); + var nonQueryParamSet = new $$UMFP.ParamSet(nonQueryParams); + return nonQueryParamSet.$$equals(fromParams, toParams); + } + + // If reload was not explicitly requested + // and we're transitioning to the same state we're already in + // and the locals didn't change + // or they changed in a way that doesn't merit reloading + // (reloadOnParams:false, or reloadOnSearch.false and only search params changed) + // Then return true. + if (!options.reload && to === from && + (locals === from.locals || (to.self.reloadOnSearch === false && nonSearchParamsEqual(from, fromParams, toParams)))) { + return true; + } + } +} + +angular.module('ui.router.state') + .factory('$stateParams', function () { return {}; }) + .provider('$state', $StateProvider); + + +$ViewProvider.$inject = []; +function $ViewProvider() { + + this.$get = $get; + /** + * @ngdoc object + * @name ui.router.state.$view + * + * @requires ui.router.util.$templateFactory + * @requires $rootScope + * + * @description + * + */ + $get.$inject = ['$rootScope', '$templateFactory']; + function $get( $rootScope, $templateFactory) { + return { + // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... }) + /** + * @ngdoc function + * @name ui.router.state.$view#load + * @methodOf ui.router.state.$view + * + * @description + * + * @param {string} name name + * @param {object} options option object. + */ + load: function load(name, options) { + var result, defaults = { + template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {} + }; + options = extend(defaults, options); + + if (options.view) { + result = $templateFactory.fromConfig(options.view, options.params, options.locals); + } + return result; + } + }; + } +} + +angular.module('ui.router.state').provider('$view', $ViewProvider); + +/** + * @ngdoc object + * @name ui.router.state.$uiViewScrollProvider + * + * @description + * Provider that returns the {@link ui.router.state.$uiViewScroll} service function. + */ +function $ViewScrollProvider() { + + var useAnchorScroll = false; + + /** + * @ngdoc function + * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll + * @methodOf ui.router.state.$uiViewScrollProvider + * + * @description + * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for + * scrolling based on the url anchor. + */ + this.useAnchorScroll = function () { + useAnchorScroll = true; + }; + + /** + * @ngdoc object + * @name ui.router.state.$uiViewScroll + * + * @requires $anchorScroll + * @requires $timeout + * + * @description + * When called with a jqLite element, it scrolls the element into view (after a + * `$timeout` so the DOM has time to refresh). + * + * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, + * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}. + */ + this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { + if (useAnchorScroll) { + return $anchorScroll; + } + + return function ($element) { + return $timeout(function () { + $element[0].scrollIntoView(); + }, 0, false); + }; + }]; +} + +angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); + +var ngMajorVer = angular.version.major; +var ngMinorVer = angular.version.minor; +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-view + * + * @requires ui.router.state.$state + * @requires $compile + * @requires $controller + * @requires $injector + * @requires ui.router.state.$uiViewScroll + * @requires $document + * + * @restrict ECA + * + * @description + * The ui-view directive tells $state where to place your templates. + * + * @param {string=} name A view name. The name should be unique amongst the other views in the + * same state. You can have views of the same name that live in different states. + * + * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window + * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll + * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you + * scroll ui-view elements into view when they are populated during a state activation. + * + * @param {string=} noanimation If truthy, the non-animated renderer will be selected (no animations + * will be applied to the ui-view) + * + * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) + * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.* + * + * @param {string=} onload Expression to evaluate whenever the view updates. + * + * @example + * A view can be unnamed or named. + *
+ * 
+ * 
+ * + * + *
+ *
+ * + * You can only have one unnamed view within any template (or root html). If you are only using a + * single view and it is unnamed then you can populate it like so: + *
+ * 
+ * $stateProvider.state("home", { + * template: "

HELLO!

" + * }) + *
+ * + * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`} + * config property, by name, in this case an empty name: + *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "": {
+ *       template: "

HELLO!

" + * } + * } + * }) + *
+ * + * But typically you'll only use the views property if you name your view or have more than one view + * in the same template. There's not really a compelling reason to name a view if its the only one, + * but you could if you wanted, like so: + *
+ * 
+ *
+ *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "main": {
+ *       template: "

HELLO!

" + * } + * } + * }) + *
+ * + * Really though, you'll use views to set up multiple views: + *
+ * 
+ *
+ *
+ *
+ * + *
+ * $stateProvider.state("home", {
+ *   views: {
+ *     "": {
+ *       template: "

HELLO!

" + * }, + * "chart": { + * template: "" + * }, + * "data": { + * template: "" + * } + * } + * }) + *
+ * + * Examples for `autoscroll`: + * + *
+ * 
+ * 
+ *
+ * 
+ * 
+ * 
+ * 
+ * 
+ */ +$ViewDirective.$inject = ['$state', '$injector', '$uiViewScroll', '$interpolate']; +function $ViewDirective( $state, $injector, $uiViewScroll, $interpolate) { + + function getService() { + return ($injector.has) ? function(service) { + return $injector.has(service) ? $injector.get(service) : null; + } : function(service) { + try { + return $injector.get(service); + } catch (e) { + return null; + } + }; + } + + var service = getService(), + $animator = service('$animator'), + $animate = service('$animate'); + + // Returns a set of DOM manipulation functions based on which Angular version + // it should use + function getRenderer(attrs, scope) { + var statics = { + enter: function (element, target, cb) { target.after(element); cb(); }, + leave: function (element, cb) { element.remove(); cb(); } + }; + + if (!!attrs.noanimation) return statics; + + function animEnabled(element) { + if (ngMajorVer === 1 && ngMinorVer >= 4) return !!$animate.enabled(element); + if (ngMajorVer === 1 && ngMinorVer >= 2) return !!$animate.enabled(); + return (!!$animator); + } + + // ng 1.2+ + if ($animate) { + return { + enter: function(element, target, cb) { + if (!animEnabled(element)) { + statics.enter(element, target, cb); + } else if (angular.version.minor > 2) { + $animate.enter(element, null, target).then(cb); + } else { + $animate.enter(element, null, target, cb); + } + }, + leave: function(element, cb) { + if (!animEnabled(element)) { + statics.leave(element, cb); + } else if (angular.version.minor > 2) { + $animate.leave(element).then(cb); + } else { + $animate.leave(element, cb); + } + } + }; + } + + // ng 1.1.5 + if ($animator) { + var animate = $animator && $animator(scope, attrs); + + return { + enter: function(element, target, cb) {animate.enter(element, null, target); cb(); }, + leave: function(element, cb) { animate.leave(element); cb(); } + }; + } + + return statics; + } + + var directive = { + restrict: 'ECA', + terminal: true, + priority: 400, + transclude: 'element', + compile: function (tElement, tAttrs, $transclude) { + return function (scope, $element, attrs) { + var previousEl, currentEl, currentScope, latestLocals, + onloadExp = attrs.onload || '', + autoScrollExp = attrs.autoscroll, + renderer = getRenderer(attrs, scope); + + scope.$on('$stateChangeSuccess', function() { + updateView(false); + }); + + updateView(true); + + function cleanupLastView() { + var _previousEl = previousEl; + var _currentScope = currentScope; + + if (_currentScope) { + _currentScope._willBeDestroyed = true; + } + + function cleanOld() { + if (_previousEl) { + _previousEl.remove(); + } + + if (_currentScope) { + _currentScope.$destroy(); + } + } + + if (currentEl) { + renderer.leave(currentEl, function() { + cleanOld(); + previousEl = null; + }); + + previousEl = currentEl; + } else { + cleanOld(); + previousEl = null; + } + + currentEl = null; + currentScope = null; + } + + function updateView(firstTime) { + var newScope, + name = getUiViewName(scope, attrs, $element, $interpolate), + previousLocals = name && $state.$current && $state.$current.locals[name]; + + if (!firstTime && previousLocals === latestLocals || scope._willBeDestroyed) return; // nothing to do + newScope = scope.$new(); + latestLocals = $state.$current.locals[name]; + + /** + * @ngdoc event + * @name ui.router.state.directive:ui-view#$viewContentLoading + * @eventOf ui.router.state.directive:ui-view + * @eventType emits on ui-view directive scope + * @description + * + * Fired once the view **begins loading**, *before* the DOM is rendered. + * + * @param {Object} event Event object. + * @param {string} viewName Name of the view. + */ + newScope.$emit('$viewContentLoading', name); + + var clone = $transclude(newScope, function(clone) { + renderer.enter(clone, $element, function onUiViewEnter() { + if(currentScope) { + currentScope.$emit('$viewContentAnimationEnded'); + } + + if (angular.isDefined(autoScrollExp) && !autoScrollExp || scope.$eval(autoScrollExp)) { + $uiViewScroll(clone); + } + }); + cleanupLastView(); + }); + + currentEl = clone; + currentScope = newScope; + /** + * @ngdoc event + * @name ui.router.state.directive:ui-view#$viewContentLoaded + * @eventOf ui.router.state.directive:ui-view + * @eventType emits on ui-view directive scope + * @description + * Fired once the view is **loaded**, *after* the DOM is rendered. + * + * @param {Object} event Event object. + * @param {string} viewName Name of the view. + */ + currentScope.$emit('$viewContentLoaded', name); + currentScope.$eval(onloadExp); + } + }; + } + }; + + return directive; +} + +$ViewDirectiveFill.$inject = ['$compile', '$controller', '$state', '$interpolate']; +function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate) { + return { + restrict: 'ECA', + priority: -400, + compile: function (tElement) { + var initial = tElement.html(); + return function (scope, $element, attrs) { + var current = $state.$current, + name = getUiViewName(scope, attrs, $element, $interpolate), + locals = current && current.locals[name]; + + if (! locals) { + return; + } + + $element.data('$uiView', { name: name, state: locals.$$state }); + $element.html(locals.$template ? locals.$template : initial); + + var link = $compile($element.contents()); + + if (locals.$$controller) { + locals.$scope = scope; + locals.$element = $element; + var controller = $controller(locals.$$controller, locals); + if (locals.$$controllerAs) { + scope[locals.$$controllerAs] = controller; + } + $element.data('$ngControllerController', controller); + $element.children().data('$ngControllerController', controller); + } + + link(scope); + }; + } + }; +} + +/** + * Shared ui-view code for both directives: + * Given scope, element, and its attributes, return the view's name + */ +function getUiViewName(scope, attrs, element, $interpolate) { + var name = $interpolate(attrs.uiView || attrs.name || '')(scope); + var inherited = element.inheritedData('$uiView'); + return name.indexOf('@') >= 0 ? name : (name + '@' + (inherited ? inherited.state.name : '')); +} + +angular.module('ui.router.state').directive('uiView', $ViewDirective); +angular.module('ui.router.state').directive('uiView', $ViewDirectiveFill); + +function parseStateRef(ref, current) { + var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed; + if (preparsed) ref = current + '(' + preparsed[1] + ')'; + parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); + if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); + return { state: parsed[1], paramExpr: parsed[3] || null }; +} + +function stateContext(el) { + var stateData = el.parent().inheritedData('$uiView'); + + if (stateData && stateData.state && stateData.state.name) { + return stateData.state; + } +} + +function getTypeInfo(el) { + // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. + var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]'; + var isForm = el[0].nodeName === "FORM"; + + return { + attr: isForm ? "action" : (isSvg ? 'xlink:href' : 'href'), + isAnchor: el.prop("tagName").toUpperCase() === "A", + clickable: !isForm + }; +} + +function clickHook(el, $state, $timeout, type, current) { + return function(e) { + var button = e.which || e.button, target = current(); + + if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || el.attr('target'))) { + // HACK: This is to allow ng-clicks to be processed before the transition is initiated: + var transition = $timeout(function() { + $state.go(target.state, target.params, target.options); + }); + e.preventDefault(); + + // if the state has no URL, ignore one preventDefault from the directive. + var ignorePreventDefaultCount = type.isAnchor && !target.href ? 1: 0; + + e.preventDefault = function() { + if (ignorePreventDefaultCount-- <= 0) $timeout.cancel(transition); + }; + } + }; +} + +function defaultOpts(el, $state) { + return { relative: stateContext(el) || $state.$current, inherit: true }; +} + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-sref + * + * @requires ui.router.state.$state + * @requires $timeout + * + * @restrict A + * + * @description + * A directive that binds a link (`` tag) to a state. If the state has an associated + * URL, the directive will automatically generate & update the `href` attribute via + * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking + * the link will trigger a state transition with optional parameters. + * + * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be + * handled natively by the browser. + * + * You can also use relative state paths within ui-sref, just like the relative + * paths passed to `$state.go()`. You just need to be aware that the path is relative + * to the state that the link lives in, in other words the state that loaded the + * template containing the link. + * + * You can specify options to pass to {@link ui.router.state.$state#go $state.go()} + * using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`, + * and `reload`. + * + * @example + * Here's an example of how you'd use ui-sref and how it would compile. If you have the + * following template: + *
+ * Home | About | Next page
+ *
+ * 
+ * 
+ * + * Then the compiled html would be (assuming Html5Mode is off and current state is contacts): + *
+ * Home | About | Next page
+ *
+ * 
    + *
  • + * Joe + *
  • + *
  • + * Alice + *
  • + *
  • + * Bob + *
  • + *
+ * + * Home + *
+ * + * @param {string} ui-sref 'stateName' can be any valid absolute or relative state + * @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()} + */ +$StateRefDirective.$inject = ['$state', '$timeout']; +function $StateRefDirective($state, $timeout) { + return { + restrict: 'A', + require: ['?^uiSrefActive', '?^uiSrefActiveEq'], + link: function(scope, element, attrs, uiSrefActive) { + var ref = parseStateRef(attrs.uiSref, $state.current.name); + var def = { state: ref.state, href: null, params: null }; + var type = getTypeInfo(element); + var active = uiSrefActive[1] || uiSrefActive[0]; + + def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {}); + + var update = function(val) { + if (val) def.params = angular.copy(val); + def.href = $state.href(ref.state, def.params, def.options); + + if (active) active.$$addStateInfo(ref.state, def.params); + if (def.href !== null) attrs.$set(type.attr, def.href); + }; + + if (ref.paramExpr) { + scope.$watch(ref.paramExpr, function(val) { if (val !== def.params) update(val); }, true); + def.params = angular.copy(scope.$eval(ref.paramExpr)); + } + update(); + + if (!type.clickable) return; + element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; })); + } + }; +} + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-state + * + * @requires ui.router.state.uiSref + * + * @restrict A + * + * @description + * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition, + * params and override options. + * + * @param {string} ui-state 'stateName' can be any valid absolute or relative state + * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#href $state.href()} + * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#go $state.go()} + */ +$StateRefDynamicDirective.$inject = ['$state', '$timeout']; +function $StateRefDynamicDirective($state, $timeout) { + return { + restrict: 'A', + require: ['?^uiSrefActive', '?^uiSrefActiveEq'], + link: function(scope, element, attrs, uiSrefActive) { + var type = getTypeInfo(element); + var active = uiSrefActive[1] || uiSrefActive[0]; + var group = [attrs.uiState, attrs.uiStateParams || null, attrs.uiStateOpts || null]; + var watch = '[' + group.map(function(val) { return val || 'null'; }).join(', ') + ']'; + var def = { state: null, params: null, options: null, href: null }; + + function runStateRefLink (group) { + def.state = group[0]; def.params = group[1]; def.options = group[2]; + def.href = $state.href(def.state, def.params, def.options); + + if (active) active.$$addStateInfo(def.state, def.params); + if (def.href) attrs.$set(type.attr, def.href); + } + + scope.$watch(watch, runStateRefLink, true); + runStateRefLink(scope.$eval(watch)); + + if (!type.clickable) return; + element.bind("click", clickHook(element, $state, $timeout, type, function() { return def; })); + } + }; +} + + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-sref-active + * + * @requires ui.router.state.$state + * @requires ui.router.state.$stateParams + * @requires $interpolate + * + * @restrict A + * + * @description + * A directive working alongside ui-sref to add classes to an element when the + * related ui-sref directive's state is active, and removing them when it is inactive. + * The primary use-case is to simplify the special appearance of navigation menus + * relying on `ui-sref`, by having the "active" state's menu button appear different, + * distinguishing it from the inactive menu items. + * + * ui-sref-active can live on the same element as ui-sref or on a parent element. The first + * ui-sref-active found at the same level or above the ui-sref will be used. + * + * Will activate when the ui-sref's target state or any child state is active. If you + * need to activate only when the ui-sref target state is active and *not* any of + * it's children, then you will use + * {@link ui.router.state.directive:ui-sref-active-eq ui-sref-active-eq} + * + * @example + * Given the following template: + *
+ * 
+ * 
+ * + * + * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins", + * the resulting HTML will appear as (note the 'active' class): + *
+ * 
+ * 
+ * + * The class name is interpolated **once** during the directives link time (any further changes to the + * interpolated value are ignored). + * + * Multiple classes may be specified in a space-separated format: + *
+ * 
    + *
  • + * link + *
  • + *
+ *
+ * + * It is also possible to pass ui-sref-active an expression that evaluates + * to an object hash, whose keys represent active class names and whose + * values represent the respective state names/globs. + * ui-sref-active will match if the current active state **includes** any of + * the specified state names/globs, even the abstract ones. + * + * @Example + * Given the following template, with "admin" being an abstract state: + *
+ * 
+ * Roles + *
+ *
+ * + * When the current state is "admin.roles" the "active" class will be applied + * to both the
and elements. It is important to note that the state + * names/globs passed to ui-sref-active shadow the state provided by ui-sref. + */ + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-sref-active-eq + * + * @requires ui.router.state.$state + * @requires ui.router.state.$stateParams + * @requires $interpolate + * + * @restrict A + * + * @description + * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate + * when the exact target state used in the `ui-sref` is active; no child states. + * + */ +$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate']; +function $StateRefActiveDirective($state, $stateParams, $interpolate) { + return { + restrict: "A", + controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) { + var states = [], activeClasses = {}, activeEqClass, uiSrefActive; + + // There probably isn't much point in $observing this + // uiSrefActive and uiSrefActiveEq share the same directive object with some + // slight difference in logic routing + activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope); + + try { + uiSrefActive = $scope.$eval($attrs.uiSrefActive); + } catch (e) { + // Do nothing. uiSrefActive is not a valid expression. + // Fall back to using $interpolate below + } + uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope); + if (isObject(uiSrefActive)) { + forEach(uiSrefActive, function(stateOrName, activeClass) { + if (isString(stateOrName)) { + var ref = parseStateRef(stateOrName, $state.current.name); + addState(ref.state, $scope.$eval(ref.paramExpr), activeClass); + } + }); + } + + // Allow uiSref to communicate with uiSrefActive[Equals] + this.$$addStateInfo = function (newState, newParams) { + // we already got an explicit state provided by ui-sref-active, so we + // shadow the one that comes from ui-sref + if (isObject(uiSrefActive) && states.length > 0) { + return; + } + addState(newState, newParams, uiSrefActive); + update(); + }; + + $scope.$on('$stateChangeSuccess', update); + + function addState(stateName, stateParams, activeClass) { + var state = $state.get(stateName, stateContext($element)); + var stateHash = createStateHash(stateName, stateParams); + + states.push({ + state: state || { name: stateName }, + params: stateParams, + hash: stateHash + }); + + activeClasses[stateHash] = activeClass; + } + + /** + * @param {string} state + * @param {Object|string} [params] + * @return {string} + */ + function createStateHash(state, params) { + if (!isString(state)) { + throw new Error('state should be a string'); + } + if (isObject(params)) { + return state + toJson(params); + } + params = $scope.$eval(params); + if (isObject(params)) { + return state + toJson(params); + } + return state; + } + + // Update route state + function update() { + for (var i = 0; i < states.length; i++) { + if (anyMatch(states[i].state, states[i].params)) { + addClass($element, activeClasses[states[i].hash]); + } else { + removeClass($element, activeClasses[states[i].hash]); + } + + if (exactMatch(states[i].state, states[i].params)) { + addClass($element, activeEqClass); + } else { + removeClass($element, activeEqClass); + } + } + } + + function addClass(el, className) { $timeout(function () { el.addClass(className); }); } + function removeClass(el, className) { el.removeClass(className); } + function anyMatch(state, params) { return $state.includes(state.name, params); } + function exactMatch(state, params) { return $state.is(state.name, params); } + + update(); + }] + }; +} + +angular.module('ui.router.state') + .directive('uiSref', $StateRefDirective) + .directive('uiSrefActive', $StateRefActiveDirective) + .directive('uiSrefActiveEq', $StateRefActiveDirective) + .directive('uiState', $StateRefDynamicDirective); + +/** + * @ngdoc filter + * @name ui.router.state.filter:isState + * + * @requires ui.router.state.$state + * + * @description + * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}. + */ +$IsStateFilter.$inject = ['$state']; +function $IsStateFilter($state) { + var isFilter = function (state, params) { + return $state.is(state, params); + }; + isFilter.$stateful = true; + return isFilter; +} + +/** + * @ngdoc filter + * @name ui.router.state.filter:includedByState + * + * @requires ui.router.state.$state + * + * @description + * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}. + */ +$IncludedByStateFilter.$inject = ['$state']; +function $IncludedByStateFilter($state) { + var includesFilter = function (state, params, options) { + return $state.includes(state, params, options); + }; + includesFilter.$stateful = true; + return includesFilter; +} + +angular.module('ui.router.state') + .filter('isState', $IsStateFilter) + .filter('includedByState', $IncludedByStateFilter); +})(window, window.angular); diff --git a/my-todolist/lib/angular-resource.min.js b/my-todolist/lib/angular-resource.min.js new file mode 100644 index 0000000..ca41c3a --- /dev/null +++ b/my-todolist/lib/angular-resource.min.js @@ -0,0 +1,15 @@ +/* + AngularJS v1.6.2-build.5252+sha.7019d9f + (c) 2010-2017 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(W,b){'use strict';function K(q,g){g=g||{};b.forEach(g,function(b,h){delete g[h]});for(var h in q)!q.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(g[h]=q[h]);return g}var B=b.$$minErr("$resource"),Q=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;b.module("ngResource",["ng"]).provider("$resource",function(){var q=/^https?:\/\/\[[^\]]*][^/]*/,g=this;this.defaults={stripTrailingSlashes:!0,cancellable:!1,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"}, +"delete":{method:"DELETE"}}};this.$get=["$http","$log","$q","$timeout",function(h,P,L,M){function C(b,e){this.template=b;this.defaults=p({},g.defaults,e);this.urlParams={}}function x(D,e,u,m){function c(a,d){var c={};d=p({},e,d);t(d,function(d,l){y(d)&&(d=d(a));var f;if(d&&d.charAt&&"@"===d.charAt(0)){f=a;var k=d.substr(1);if(null==k||""===k||"hasOwnProperty"===k||!Q.test("."+k))throw B("badmember",k);for(var k=k.split("."),e=0,g=k.length;e").append(a).html();try{return a[0].nodeType===Ja?P(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w-]+)/,function(a,b){return"<"+P(b)})}catch(c){return P(d)}}function Kc(a){try{return decodeURIComponent(a)}catch(b){}}function Lc(a){var b={};q((a||"").split("&"),function(a){var c,f,e;a&&(f=a=a.replace(/\+/g,"%20"), +c=a.indexOf("="),-1!==c&&(f=a.substring(0,c),e=a.substring(c+1)),f=Kc(f),v(f)&&(e=v(e)?Kc(e):!0,va.call(b,f)?C(b[f])?b[f].push(e):b[f]=[b[f],e]:b[f]=e))});return b}function Xb(a){var b=[];q(a,function(a,c){C(a)?q(a,function(a){b.push(ka(c,!0)+(!0===a?"":"="+ka(a,!0)))}):b.push(ka(c,!0)+(!0===a?"":"="+ka(a,!0)))});return b.length?b.join("&"):""}function db(a){return ka(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ka(a,b){return encodeURIComponent(a).replace(/%40/gi, +"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function oe(a,b){var d,c,f=Ka.length;for(c=0;c protocol indicates an extension, document.location.href does not match."))}function Mc(a,b,d){F(d)||(d={});d=R({strictDi:!1},d);var c=function(){a=D(a);if(a.injector()){var c=a[0]===z.document?"document":ya(a);throw Ga("btstrpd",c.replace(//,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]); +b.unshift("ng");c=eb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,d,c){a.$apply(function(){b.data("$injector",c);d(b)(a)})}]);return c},f=/^NG_ENABLE_DEBUG_INFO!/,e=/^NG_DEFER_BOOTSTRAP!/;z&&f.test(z.name)&&(d.debugInfoEnabled=!0,z.name=z.name.replace(f,""));if(z&&!e.test(z.name))return c();z.name=z.name.replace(e,"");$.resumeBootstrap=function(a){q(a,function(a){b.push(a)});return c()};y($.resumeDeferredBootstrap)&&$.resumeDeferredBootstrap()}function re(){z.name= +"NG_ENABLE_DEBUG_INFO!"+z.name;z.location.reload()}function se(a){a=$.element(a).injector();if(!a)throw Ga("test");return a.get("$$testability")}function Nc(a,b){b=b||"_";return a.replace(te,function(a,c){return(c?b:"")+a.toLowerCase()})}function ue(){var a;if(!Oc){var b=sb();(oa=x(b)?z.jQuery:b?z[b]:void 0)&&oa.fn.on?(D=oa,R(oa.fn,{scope:Oa.scope,isolateScope:Oa.isolateScope,controller:Oa.controller,injector:Oa.injector,inheritedData:Oa.inheritedData}),a=oa.cleanData,oa.cleanData=function(b){for(var c, +f=0,e;null!=(e=b[f]);f++)(c=oa._data(e,"events"))&&c.$destroy&&oa(e).triggerHandler("$destroy");a(b)}):D=X;$.element=D;Oc=!0}}function fb(a,b,d){if(!a)throw Ga("areq",b||"?",d||"required");return a}function tb(a,b,d){d&&C(a)&&(a=a[a.length-1]);fb(y(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Pa(a,b){if("hasOwnProperty"===a)throw Ga("badname",b);}function Pc(a,b,d){if(!b)return a;b=b.split(".");for(var c,f=a,e=b.length,g=0;g")+c[2];for(c=c[0];c--;)d=d.lastChild;e=ab(e,d.childNodes);d=f.firstChild;d.textContent=""}else e.push(b.createTextNode(a)); +f.textContent="";f.innerHTML="";q(e,function(a){f.appendChild(a)});return f}function X(a){if(a instanceof X)return a;var b;E(a)&&(a=S(a),b=!0);if(!(this instanceof X)){if(b&&"<"!==a.charAt(0))throw ac("nosel");return new X(a)}if(b){b=z.document;var d;a=(d=Zf.exec(a))?[b.createElement(d[1])]:(d=Zc(a,b))?d.childNodes:[];bc(this,a)}else y(a)?$c(a):bc(this,a)}function cc(a){return a.cloneNode(!0)}function yb(a,b){b||hb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,f=d.length;cl&&this.remove(p.key);return b}},get:function(a){if(l";b=ua.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function Ma(a, +b){try{a.addClass(b)}catch(c){}}function ba(a,b,c,d,e){a instanceof D||(a=D(a));var f=Na(a,b,a,c,d,e);ba.$$addScopeClass(a);var g=null;return function(b,c,d){if(!a)throw ea("multilink");fb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var h=d.parentBoundTranscludeFn,k=d.transcludeControllers;d=d.futureParentElement;h&&h.$$boundTransclude&&(h=h.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==xa(d)&&na.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==g?D(ha(g,D("
").append(a).html())): +c?Oa.clone.call(a):a;if(k)for(var l in k)d.data("$"+l+"Controller",k[l].instance);ba.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,h);c||(a=f=null);return d}}function Na(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,n,p,r;if(K)for(r=Array(c.length),m=0;mt.priority)break;if(w=t.scope)t.templateUrl||(F(w)?($("new/isolated scope",B||K,t,G),B=t):$("new/isolated scope",B,t,G)),K=K||t;L=t.name;if(!v&&(t.replace&&(t.templateUrl||t.template)||t.transclude&&!t.$$tlb)){for(w=z+1;v=a[w++];)if(v.transclude&&!v.$$tlb||v.replace&&(v.templateUrl||v.template)){Ma=!0;break}v=!0}!t.templateUrl&& +t.controller&&(A=A||W(),$("'"+L+"' controller",A[L],t,G),A[L]=t);if(w=t.transclude)if(J=!0,t.$$tlb||($("transclusion",u,t,G),u=t),"element"===w)U=!0,p=t.priority,T=G,G=d.$$element=D(ba.$$createComment(L,d[L])),b=G[0],la(f,wa.call(T,0),b),T[0].$$parentNode=T[0].parentNode,O=gc(Ma,T,e,p,g&&g.name,{nonTlbTranscludeDirective:u});else{var ja=W();if(F(w)){T=[];var P=W(),kb=W();q(w,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;P[a]=b;ja[b]=null;kb[b]=c});q(G.contents(),function(a){var b=P[Ca(xa(a))]; +b?(kb[b]=!0,ja[b]=ja[b]||[],ja[b].push(a)):T.push(a)});q(kb,function(a,b){if(!a)throw ea("reqslot",b);});for(var ec in ja)ja[ec]&&(ja[ec]=gc(Ma,ja[ec],e))}else T=D(cc(b)).contents();G.empty();O=gc(Ma,T,e,void 0,void 0,{needsNewScope:t.$$isolateScope||t.$$newScope});O.$$slots=ja}if(t.template)if(N=!0,$("template",I,t,G),I=t,w=y(t.template)?t.template(G,d):t.template,w=Ea(w),t.replace){g=t;T=$b.test(w)?jd(ha(t.templateNamespace,S(w))):[];b=T[0];if(1!==T.length||1!==b.nodeType)throw ea("tplrt",L,""); +la(f,G,b);E={$attr:{}};w=fc(b,[],E);var Y=a.splice(z+1,a.length-(z+1));(B||K)&&aa(w,B,K);a=a.concat(w).concat(Y);fa(d,E);E=a.length}else G.html(w);if(t.templateUrl)N=!0,$("template",I,t,G),I=t,t.replace&&(g=t),n=ga(a.splice(z,a.length-z),G,d,f,J&&O,h,k,{controllerDirectives:A,newScopeDirective:K!==t&&K,newIsolateScopeDirective:B,templateDirective:I,nonTlbTranscludeDirective:u}),E=a.length;else if(t.compile)try{Q=t.compile(G,d,O);var Z=t.$$originalDirective||t;y(Q)?m(null,bb(Z,Q),Na,M):Q&&m(bb(Z,Q.pre), +bb(Z,Q.post),Na,M)}catch(da){c(da,ya(G))}t.terminal&&(n.terminal=!0,p=Math.max(p,t.priority))}n.scope=K&&!0===K.scope;n.transcludeOnThisElement=J;n.templateOnThisElement=N;n.transclude=O;l.hasElementTranscludeDirective=U;return n}function V(a,b,c,d){var e;if(E(b)){var f=b.match(l);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw ea("ctreq",b,a);}else if(C(b))for(e= +[],g=0,f=b.length;gc.priority)&&-1!==c.restrict.indexOf(f)){k&&(c=Ub(c,{$$start:k,$$end:l}));if(!c.$$bindings){var K=m=c,r=c.name,A={isolateScope:null,bindToController:null};F(K.scope)&&(!0===K.bindToController?(A.bindToController=d(K.scope,r,!0),A.isolateScope={}):A.isolateScope=d(K.scope,r,!1));F(K.bindToController)&&(A.bindToController=d(K.bindToController,r,!0));if(A.bindToController&&!K.controller)throw ea("noctrl", +r);m=m.$$bindings=A;F(m.isolateScope)&&(c.$$isolateBindings=m.isolateScope)}b.push(c);m=c}}return m}function Z(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function pa(a,b){if("srcdoc"===b)return L.HTML;var c=xa(a);if("src"===b||"ngSrc"===b){if(-1===["img","video","audio","source","track"].indexOf(c))return L.RESOURCE_URL}else if("xlinkHref"===b||"form"===c&&"action"===b||"link"===c&&"href"===b)return L.RESOURCE_URL}function ra(a, +c,d,e,f){var g=pa(a,e),h=k[e]||f,l=b(d,!f,g,h);if(l){if("multiple"===e&&"select"===xa(a))throw ea("selmulti",ya(a));if(m.test(e))throw ea("nodomevents");c.push({priority:100,compile:function(){return{pre:function(a,c,f){c=f.$$observers||(f.$$observers=W());var k=f[e];k!==d&&(l=k&&b(k,!0,g,h),d=k);l&&(f[e]=l(a),(c[e]||(c[e]=[])).$$inter=!0,(f.$$observers&&f.$$observers[e].$$scope||a).$watch(l,function(a,b){"class"===e&&a!==b?f.$updateClass(a,b):f.$set(e,a)}))}}}})}}function la(a,b,c){var d=b[0],e= +b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=b)return a;for(;b--;){var d=a[b];(8===d.nodeType||d.nodeType===Ja&&""===d.nodeValue.trim())&&ng.call(a,b,1)}return a}function lg(a,b){if(b&&E(b))return b;if(E(a)){var d=md.exec(a);if(d)return d[3]}}function rf(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Pa(b,"controller");F(b)? +R(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function f(a,b,c,d){if(!a||!F(a.$scope))throw M("$controller")("noscp",d,b);a.$scope[b]=c}return function(e,g,h,k){var l,m,n;h=!0===h;k&&E(k)&&(n=k);if(E(e)){k=e.match(md);if(!k)throw nd("ctrlfmt",e);m=k[1];n=n||k[3];e=a.hasOwnProperty(m)?a[m]:Pc(g.$scope,m,!0)||(b?Pc(c,m,!0):void 0);if(!e)throw nd("ctrlreg",m);tb(e,m,!0)}if(h)return h=(C(e)?e[e.length-1]:e).prototype,l=Object.create(h||null),n&&f(g,n, +l,m||e.name),R(function(){var a=d.invoke(e,l,g,m);a!==l&&(F(a)||y(a))&&(l=a,n&&f(g,n,l,m||e.name));return l},{instance:l,identifier:n});l=d.instantiate(e,g,m);n&&f(g,n,l,m||e.name);return l}}]}function sf(){this.$get=["$window",function(a){return D(a.document)}]}function tf(){this.$get=["$document","$rootScope",function(a,b){function d(){f=c.hidden}var c=a[0],f=c&&c.hidden;a.on("visibilitychange",d);b.$on("$destroy",function(){a.off("visibilitychange",d)});return function(){return f}}]}function uf(){this.$get= +["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function ic(a){return F(a)?fa(a)?a.toISOString():cb(a):a}function zf(){this.$get=function(){return function(a){if(!a)return"";var b=[];Ec(a,function(a,c){null===a||x(a)||(C(a)?q(a,function(a){b.push(ka(c)+"="+ka(ic(a)))}):b.push(ka(c)+"="+ka(ic(a))))});return b.join("&")}}}function Af(){this.$get=function(){return function(a){function b(a,f,e){null===a||x(a)||(C(a)?q(a,function(a,c){b(a,f+"["+(F(a)?c:"")+"]")}):F(a)&&!fa(a)?Ec(a, +function(a,c){b(a,f+(e?"":"[")+c+(e?"":"]"))}):d.push(ka(f)+"="+ka(ic(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function jc(a,b){if(E(a)){var d=a.replace(og,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(od))||(c=(c=d.match(pg))&&qg[c[0]].test(d));c&&(a=Ic(d))}}return a}function pd(a){var b=W(),d;E(a)?q(a.split("\n"),function(a){d=a.indexOf(":");var f=P(S(a.substr(0,d)));a=S(a.substr(d+1));f&&(b[f]=b[f]?b[f]+", "+a:a)}):F(a)&&q(a,function(a,d){var e=P(d),g=S(a);e&& +(b[e]=b[e]?b[e]+", "+g:g)});return b}function qd(a){var b;return function(d){b||(b=pd(a));return d?(d=b[P(d)],void 0===d&&(d=null),d):b}}function rd(a,b,d,c){if(y(c))return c(a,b,d);q(c,function(c){a=c(a,b,d)});return a}function yf(){var a=this.defaults={transformResponse:[jc],transformRequest:[function(a){return F(a)&&"[object File]"!==na.call(a)&&"[object Blob]"!==na.call(a)&&"[object FormData]"!==na.call(a)?cb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ra(kc),put:ra(kc), +patch:ra(kc)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer",jsonpCallbackParam:"callback"},b=!1;this.useApplyAsync=function(a){return v(a)?(b=!!a,this):b};var d=this.interceptors=[];this.$get=["$browser","$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector","$sce",function(c,f,e,g,h,k,l,m){function n(b){function d(a,b){for(var c=0,e=b.length;ca?b:k.reject(b)}if(!F(b))throw M("$http")("badreq",b);if(!E(m.valueOf(b.url)))throw M("$http")("badreq",b.url);var g=R({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer,jsonpCallbackParam:a.jsonpCallbackParam},b);g.headers=function(b){var c= +a.headers,d=R({},b.headers),f,g,h,c=R({},c.common,c[P(b.method)]);a:for(f in c){g=P(f);for(h in d)if(P(h)===g)continue a;d[f]=c[f]}return e(d,ra(b))}(b);g.method=vb(g.method);g.paramSerializer=E(g.paramSerializer)?l.get(g.paramSerializer):g.paramSerializer;c.$$incOutstandingRequestCount();var h=[],n=[];b=k.resolve(g);q(u,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&n.push(a.response,a.responseError)});b=d(b,h);b=b.then(function(b){var c= +b.headers,d=rd(b.data,qd(c),void 0,b.transformRequest);x(d)&&q(c,function(a,b){"content-type"===P(b)&&delete c[b]});x(b.withCredentials)&&!x(a.withCredentials)&&(b.withCredentials=a.withCredentials);return p(b,d).then(f,f)});b=d(b,n);return b=b.finally(function(){c.$$completeOutstandingRequest(w)})}function p(c,d){function g(a){if(a){var c={};q(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function l(a,c,d,e){function f(){p(c,a,d,e)}N&& +(200<=a&&300>a?N.put(Q,[a,c,pd(d),e]):N.remove(Q));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function p(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?B.resolve:B.reject)({data:a,status:b,headers:qd(d),config:c,statusText:e})}function K(a){p(a.data,a.status,ra(a.headers()),a.statusText)}function u(){var a=n.pendingRequests.indexOf(c);-1!==a&&n.pendingRequests.splice(a,1)}var B=k.defer(),L=B.promise,N,G,T=c.headers,s="jsonp"===P(c.method),Q=c.url;s?Q=m.getTrustedResourceUrl(Q):E(Q)||(Q=m.valueOf(Q));Q=r(Q, +c.paramSerializer(c.params));s&&(Q=J(Q,c.jsonpCallbackParam));n.pendingRequests.push(c);L.then(u,u);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(N=F(c.cache)?c.cache:F(a.cache)?a.cache:O);N&&(G=N.get(Q),v(G)?G&&y(G.then)?G.then(K,K):C(G)?p(G[1],G[0],ra(G[2]),G[3]):p(G,200,{},"OK"):N.put(Q,L));x(G)&&((G=sd(c.url)?e()[c.xsrfCookieName||a.xsrfCookieName]:void 0)&&(T[c.xsrfHeaderName||a.xsrfHeaderName]=G),f(c.method,Q,d,l,T,c.timeout,c.withCredentials,c.responseType,g(c.eventHandlers), +g(c.uploadEventHandlers)));return L}function r(a,b){0=l&&(U.resolve(u),q(t.$$intervalId),delete g[t.$$intervalId]);H||a.$apply()},k);g[t.$$intervalId]=U;return t}var g={};e.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].promise.catch(w),g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),delete g[a.$$intervalId],!0):!1};return e}]}function lc(a){a=a.split("/");for(var b=a.length;b--;)a[b]= +db(a[b]);return a.join("/")}function ud(a,b){var d=Da(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Z(d.port)||sg[d.protocol]||null}function vd(a,b){if(tg.test(a))throw lb("badpath",a);var d="/"!==a.charAt(0);d&&(a="/"+a);var c=Da(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=Lc(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!==b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function mc(a,b){return a.slice(0,b.length)=== +b}function sa(a,b){if(mc(b,a))return b.substr(a.length)}function Ba(a){var b=a.indexOf("#");return-1===b?a:a.substr(0,b)}function mb(a){return a.replace(/(#.+)|#$/,"$1")}function nc(a,b,d){this.$$html5=!0;d=d||"";ud(a,this);this.$$parse=function(a){var d=sa(b,a);if(!E(d))throw lb("ipthprfx",a,b);vd(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Xb(this.$$search),d=this.$$hash?"#"+db(this.$$hash):"";this.$$url=lc(this.$$path)+(a?"?"+a:"")+d;this.$$absUrl=b+ +this.$$url.substr(1)};this.$$parseLinkUrl=function(c,f){if(f&&"#"===f[0])return this.hash(f.slice(1)),!0;var e,g;v(e=sa(a,c))?(g=e,g=d&&v(e=sa(d,e))?b+(sa("/",e)||e):a+g):v(e=sa(b,c))?g=b+e:b===c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function oc(a,b,d){ud(a,this);this.$$parse=function(c){var f=sa(a,c)||sa(b,c),e;x(f)||"#"!==f.charAt(0)?this.$$html5?e=f:(e="",x(f)&&(a=c,this.replace())):(e=sa(d,f),x(e)&&(e=f));vd(e,this);c=this.$$path;var f=a,g=/^\/[A-Z]:(\/.*)/;mc(e,f)&&(e=e.replace(f,""));g.exec(e)|| +(c=(e=g.exec(c))?e[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Xb(this.$$search),f=this.$$hash?"#"+db(this.$$hash):"";this.$$url=lc(this.$$path)+(b?"?"+b:"")+f;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ba(a)===Ba(b)?(this.$$parse(b),!0):!1}}function wd(a,b,d){this.$$html5=!0;oc.apply(this,arguments);this.$$parseLinkUrl=function(c,f){if(f&&"#"===f[0])return this.hash(f.slice(1)),!0;var e,g;a===Ba(c)?e=c:(g=sa(b,c))?e=a+d+g:b=== +c+"/"&&(e=b);e&&this.$$parse(e);return!!e};this.$$compose=function(){var b=Xb(this.$$search),f=this.$$hash?"#"+db(this.$$hash):"";this.$$url=lc(this.$$path)+(b?"?"+b:"")+f;this.$$absUrl=a+d+this.$$url}}function Jb(a){return function(){return this[a]}}function xd(a,b){return function(d){if(x(d))return this[a];this[a]=b(d);this.$$compose();return this}}function Ef(){var a="!",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return v(b)?(a=b,this):a};this.html5Mode=function(a){if(Ia(a))return b.enabled= +a,this;if(F(a)){Ia(a.enabled)&&(b.enabled=a.enabled);Ia(a.requireBase)&&(b.requireBase=a.requireBase);if(Ia(a.rewriteLinks)||E(a.rewriteLinks))b.rewriteLinks=a.rewriteLinks;return this}return b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,f,e,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,b)}var l,m;m=c.baseHref(); +var n=c.url(),p;if(b.enabled){if(!m&&b.requireBase)throw lb("nobase");p=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=f.history?nc:wd}else p=Ba(n),m=oc;var r=p.substr(0,Ba(p).lastIndexOf("/")+1);l=new m(p,r,"#"+a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var J=/^\s*(javascript|mailto):/i;e.on("click",function(a){var f=b.rewriteLinks;if(f&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!==a.which&&2!==a.button){for(var h=D(a.target);"a"!==xa(h[0]);)if(h[0]===e[0]||!(h=h.parent())[0])return;if(!E(f)|| +!x(h.attr(f))){var f=h.prop("href"),k=h.attr("href")||h.attr("xlink:href");F(f)&&"[object SVGAnimatedString]"===f.toString()&&(f=Da(f.animVal).href);J.test(f)||!f||h.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(f,k)||(a.preventDefault(),l.absUrl()!==c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}}});mb(l.absUrl())!==mb(n)&&c.url(l.absUrl(),!0);var q=!0;c.onUrlChange(function(a,b){mc(a,r)?(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=mb(a);l.$$parse(a);l.$$state= +b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(q=!1,k(c,e)))}),d.$$phase||d.$digest()):g.location.href=a});d.$watch(function(){var a=mb(c.url()),b=mb(l.absUrl()),e=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&f.history&&e!==l.$$state;if(q||m)q=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,e).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=e):(m&&h(b,g,e===l.$$state? +null:l.$$state),k(a,e)))});l.$$replace=!1});return l}]}function Ff(){var a=!0,b=this;this.debugEnabled=function(b){return v(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function f(a){var b=d.console||{},f=b[a]||b.log||w;a=!1;try{a=!!f.apply}catch(k){}return a?function(){var a=[];q(arguments,function(b){a.push(c(b))}); +return f.apply(b,a)}:function(a,b){f(a,null==b?"":b)}}return{log:f("log"),info:f("info"),warn:f("warn"),error:f("error"),debug:function(){var c=f("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function ug(a){return a+""}function vg(a,b){return"undefined"!==typeof a?a:b}function yd(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function V(a,b){var d,c,f;switch(a.type){case s.Program:d=!0;q(a.body,function(a){V(a.expression,b);d=d&&a.expression.constant});a.constant=d;break; +case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:V(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:V(a.test,b);V(a.alternate,b);V(a.consequent, +b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant=!1;a.toWatch=[a];break;case s.MemberExpression:V(a.object,b);a.computed&&V(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d=f=a.filter?!b(a.callee.name).$stateful:!1;c=[];q(a.arguments,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=f?c:[a]; +break;case s.AssignmentExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];q(a.elements,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d=!0;c=[];q(a.properties,function(a){V(a.value,b);d=d&&a.value.constant&&!a.computed;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant= +!1;a.toWatch=[];break;case s.LocalsExpression:a.constant=!1,a.toWatch=[]}}function zd(a){if(1===a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function Ad(a){return a.type===s.Identifier||a.type===s.MemberExpression}function Bd(a){if(1===a.body.length&&Ad(a.body[0].expression))return{type:s.AssignmentExpression,left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function Cd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type=== +s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function Dd(a,b){this.astBuilder=a;this.$filter=b}function Ed(a,b){this.astBuilder=a;this.$filter=b}function pc(a){return y(a.valueOf)?a.valueOf():wg.call(a)}function Gf(){var a=W(),b={"true":!0,"false":!1,"null":null,undefined:void 0},d,c;this.addLiteral=function(a,c){b[a]=c};this.setIdentifierFns=function(a,b){d=a;c=b;return this};this.$get=["$filter",function(f){function e(a,b){return null== +a||null==b?a===b:"object"===typeof a&&(a=pc(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function g(a,b,c,d,f){var g=d.inputs,h;if(1===g.length){var k=e,g=g[0];return a.$watch(function(a){var b=g(a);e(b,k)||(h=d(a,void 0,void 0,[b]),k=b&&pc(b));return h},b,c,f)}for(var l=[],m=[],n=0,I=g.length;n=c.$$state.status&&e&&e.length&&a(function(){for(var a,c,f=0,g=e.length;fa)for(b in l++,f)va.call(e,b)||(u--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1u&&(x=4-u,w[x]||(w[x]=[]),w[x].push({msg:y(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){r=!1;break a}}catch(D){e(D)}if(!(p=q.$$watchersCount&&q.$$childHead||q!==this&&q.$$nextSibling))for(;q!==this&&!(p=q.$$nextSibling);)q=q.$parent}while(q=p);if((r||v.length)&&!u--)throw H.$$phase= +null,d("infdig",b,w);}while(r||v.length);for(H.$$phase=null;ALa)throw ua("iequirks");var c=ra(pa);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Ya);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var f=c.parseAs, +e=c.getTrusted,g=c.trustAs;q(pa,function(a,b){var d=P(b);c[("parse_as_"+d).replace(sc,gb)]=function(b){return f(a,b)};c[("get_trusted_"+d).replace(sc,gb)]=function(b){return e(a,b)};c[("trust_as_"+d).replace(sc,gb)]=function(b){return g(a,b)}});return c}]}function Mf(){this.$get=["$window","$document",function(a,b){var d={},c=!(a.chrome&&(a.chrome.app&&a.chrome.app.runtime||!a.chrome.app&&a.chrome.runtime&&a.chrome.runtime.id))&&a.history&&a.history.pushState,f=Z((/android (\d+)/.exec(P((a.navigator|| +{}).userAgent))||[])[1]),e=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h=g.body&&g.body.style,k=!1,l=!1;h&&(k=!!("transition"in h||"webkitTransition"in h),l=!!("animation"in h||"webkitAnimation"in h));return{history:!(!c||4>f||e),hasEvent:function(a){if("input"===a&&La)return!1;if(x(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:za(),transitions:k,animations:l,android:f}}]}function Of(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$exceptionHandler", +"$templateCache","$http","$q","$sce",function(b,d,c,f,e){function g(h,k){g.totalPendingRequests++;if(!E(h)||x(d.get(h)))h=e.getTrustedResourceUrl(h);var l=c.defaults&&c.defaults.transformResponse;C(l)?l=l.filter(function(a){return a!==jc}):l===jc&&(l=null);return c.get(h,R({cache:d,transformResponse:l},a)).finally(function(){g.totalPendingRequests--}).then(function(a){d.put(h,a.data);return a.data},function(a){k||(a=yg("tpload",h,a.status,a.statusText),b(a));return f.reject(a)})}g.totalPendingRequests= +0;return g}]}function Pf(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];q(a,function(a){var c=$.element(a).data("$binding");c&&q(c,function(c){d?(new RegExp("(^|\\s)"+Gd(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!==c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;hc&&(c=f),c+=+a.slice(f+1),a=a.substring(0,f)):0>c&&(c=a.length);for(f=0;a.charAt(f)===uc;f++); +if(f===(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)===uc;)g--;c-=f;d=[];for(e=0;f<=g;f++,e++)d[e]=+a.charAt(f)}c>Qd&&(d=d.splice(0,Qd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function Gg(a,b,d,c){var f=a.d,e=f.length-a.i;b=x(b)?Math.min(Math.max(d,e),c):+b;d=b+a.i;c=f[d];if(0d-1){for(c=0;c>d;c--)f.unshift(0),a.i++;f.unshift(1);a.i++}else f[d- +1]++;for(;eh;)k.unshift(0),h++;0=b.lgSize&&h.unshift(k.splice(-b.lgSize,k.length).join(""));k.length> +b.gSize;)h.unshift(k.splice(-b.gSize,k.length).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);e.length&&(k+=c+e.join(""));f&&(k+="e+"+f)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Kb(a,b,d,c){var f="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,f="-");for(a=""+a;a.length-d)e+=d;0===e&&-12===d&&(e=12);return Kb(e,b,c,f)}}function nb(a,b,d){return function(c,f){var e= +c["get"+a](),g=vb((d?"STANDALONE":"")+(b?"SHORT":"")+a);return f[g][e]}}function Rd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Sd(a){return function(b){var d=Rd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Kb(b,a)}}function vc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Ld(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var e=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear, +k=b[8]?a.setUTCHours:a.setHours;b[9]&&(e=Z(b[9]+b[10]),g=Z(b[9]+b[11]));h.call(a,Z(b[1]),Z(b[2])-1,Z(b[3]));e=Z(b[4]||0)-e;g=Z(b[5]||0)-g;h=Z(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,e,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,e){var g="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;E(c)&&(c=Hg.test(c)?Z(c):b(c));Y(c)&&(c=new Date(c));if(!fa(c)||!isFinite(c.getTime()))return c; +for(;d;)(l=Ig.exec(d))?(h=ab(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();e&&(m=Jc(e,m),c=Wb(c,e,!0));q(h,function(b){k=Jg[b];g+=k?k(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Ag(){return function(a,b){x(b)&&(b=2);return cb(a,b)}}function Bg(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):Z(b);if(ga(b))return a;Y(a)&&(a=a.toString());if(!ta(a))return a;d=!d||isNaN(d)?0:Z(d);d=0>d?Math.max(0,a.length+ +d):d;return 0<=b?wc(a,d,d+b):0===d?wc(a,b,a.length):wc(a,Math.max(0,d+b),d)}}function wc(a,b,d){return E(a)?a.slice(b,d):wa.call(a,b,d)}function Nd(a){function b(b){return b.map(function(b){var c=1,d=Ya;if(y(b))d=b;else if(E(b)){if("+"===b.charAt(0)||"-"===b.charAt(0))c="-"===b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var f=d(),d=function(a){return a[f]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}} +function c(a,b){var c=0,d=a.type,k=b.type;if(d===k){var k=a.value,l=b.value;"string"===d?(k=k.toLowerCase(),l=l.toLowerCase()):"object"===d&&(F(k)&&(k=a.index),F(l)&&(l=b.index));k!==l&&(c=kb||37<=b&&40>=b||m(a,this,this.value)});if(f.hasEvent("paste"))b.on("paste cut", +m)}b.on("change",l);if(Xd[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=e.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Nb(a,b){return function(d,c){var f,e;if(fa(d))return d;if(E(d)){'"'===d.charAt(0)&&'"'===d.charAt(d.length-1)&&(d=d.substring(1,d.length-1));if(Kg.test(d))return new Date(d); +a.lastIndex=0;if(f=a.exec(d))return f.shift(),e=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},q(f,function(a,c){c=s};g.$observe("min",function(a){s=p(a);h.$validate()})}if(v(g.max)||g.ngMax){var u; +h.$validators.max=function(a){return!n(a)||x(u)||d(a)<=u};g.$observe("max",function(a){u=p(a);h.$validate()})}}}function yc(a,b,d,c){(c.$$hasNativeValidators=F(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?void 0:a})}function Yd(a){a.$$parserName="number";a.$parsers.push(function(b){if(a.$isEmpty(b))return null;if(Lg.test(b))return parseFloat(b)});a.$formatters.push(function(b){if(!a.$isEmpty(b)){if(!Y(b))throw qb("numfmt",b);b=b.toString()}return b})} +function Ta(a){v(a)&&!Y(a)&&(a=parseFloat(a));return ga(a)?void 0:a}function zc(a){var b=a.toString(),d=b.indexOf(".");return-1===d?-1a&&(a=/e-(\d+)$/.exec(b))?Number(a[1]):0:b.length-d-1}function Zd(a,b,d){a=Number(a);if((a|0)!==a||(b|0)!==b||(d|0)!==d){var c=Math.max(zc(a),zc(b),zc(d)),c=Math.pow(10,c);a*=c;b*=c;d*=c}return 0===(a-b)%d}function $d(a,b,d,c,f){if(v(c)){a=a(c);if(!a.constant)throw qb("constexpr",d,c);return a(b)}return f}function Ac(a,b){function d(a,b){if(!a||!a.length)return[]; +if(!b||!b.length)return a;var c=[],d=0;a:for(;d(?:<\/\1>|)$/,$b=/<|&#?\w+;/,Xf=/<([\w:-]+)/,Yf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,ha={option:[1,'"],thead:[1,"","
"], +col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ha.optgroup=ha.option;ha.tbody=ha.tfoot=ha.colgroup=ha.caption=ha.thead;ha.th=ha.td;var eg=z.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Oa=X.prototype={ready:$c,toString:function(){var a=[];q(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?D(this[a]):D(this[this.length+ +a])},length:0,push:Og,sort:[].sort,splice:[].splice},Gb={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){Gb[P(a)]=a});var ed={};q("input select option textarea button form details".split(" "),function(a){ed[a]=!0});var ld={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern",ngStep:"step"};q({data:dc,removeData:hb,hasData:function(a){for(var b in ib[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b< +d;b++)hb(a[b])}},function(a,b){X[b]=a});q({data:dc,inheritedData:Eb,scope:function(a){return D.data(a,"$scope")||Eb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return D.data(a,"$isolateScope")||D.data(a,"$isolateScopeNoTemplate")},controller:bd,injector:function(a){return Eb(a,"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:Bb,css:function(a,b,d){b=xb(b.replace(Pg,"ms-"));if(v(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType; +if(c!==Ja&&2!==c&&8!==c&&a.getAttribute){var c=P(b),f=Gb[c];if(v(d))null===d||!1===d&&f?a.removeAttribute(b):a.setAttribute(b,f?c:d);else return a=a.getAttribute(b),f&&null!==a&&(a=c),null===a?void 0:a}},prop:function(a,b,d){if(v(d))a[b]=d;else return a[b]},text:function(){function a(a,d){if(x(d)){var c=a.nodeType;return 1===c||c===Ja?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(x(b)){if(a.multiple&&"select"===xa(a)){var d=[];q(a.options,function(a){a.selected&&d.push(a.value|| +a.text)});return d}return a.value}a.value=b},html:function(a,b){if(x(b))return a.innerHTML;yb(a,!0);a.innerHTML=b},empty:cd},function(a,b){X.prototype[b]=function(b,c){var f,e,g=this.length;if(a!==cd&&x(2===a.length&&a!==Bb&&a!==bd?b:c)){if(F(b)){for(f=0;f/, +hg=/^[^(]*\(\s*([^)]*)\)/m,Qg=/,/,Rg=/^\s*(_?)(\S+?)\1\s*$/,fg=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,da=M("$injector");eb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw E(d)&&d||(d=a.name||ig(a)),da("strictdi",d);b=fd(a);q(b[1].split(Qg),function(a){a.replace(Rg,function(a,b,d){c.push(d)})})}a.$inject=c}}else C(a)?(b=a.length-1,tb(a[b],"fn"),c=a.slice(0,b)):tb(a,"fn",!0);return c};var be=M("$animate"),lf=function(){this.$get=w},mf=function(){var a= +new Qa,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function f(a,b,c){var d=!1;b&&(b=E(b)?b.split(" "):C(b)?b:[],q(b,function(b){b&&(d=!0,a[b]=c)}));return d}function e(){q(b,function(b){var c=a.get(b);if(c){var d=jg(b.attr("class")),e="",f="";q(c,function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});q(b,function(a){e&&Db(a,e);f&&Cb(a,f)});a.remove(b)}});b.length=0}return{enabled:w,on:w,off:w,pin:w,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from); +k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=f(k,h,!0),l=f(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(e);g=new d;g.complete();return g}}}]},jf=["$provide",function(a){var b=this;this.$$registeredAnimations=Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw be("notcsel",d);var f=d+"-animation";b.$$registeredAnimations[d.substr(1)]=f;a.factory(f,c)};this.classNameFilter=function(a){if(1===arguments.length&& +(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw be("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h <= >= && || ! = |".split(" "),function(a){Qb[a]=!0});var Ug={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},qc=function(a){this.options=a};qc.prototype={constructor:qc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue? +this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"=== +a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=v(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw Ua("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index","<=",">=");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a}, +unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=Fa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:s.Literal,value:this.options.literals[this.consume().text]}: +this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE"); +return a},filter:function(a){a=[a];for(var b={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}}, +arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:s.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")? +(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");return{type:s.ObjectExpression,properties:a}},throwError:function(a,b){throw Ua("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw Ua("ueoe", +this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw Ua("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,f){if(this.tokens.length>a){a=this.tokens[a];var e=a.text;if(e===b||e===d||e===c||e===f||!(b||d||c||f))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:s.ThisExpression}, +$locals:{type:s.LocalsExpression}}};Dd.prototype={compile:function(a){var b=this;a=this.astBuilder.ast(a);this.state={nextId:0,filters:{},fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};V(a,b.$filter);var d="",c;this.stage="assign";if(c=Bd(a))this.state.computing="assign",d=this.nextId(),this.recurse(c,d),this.return_(d),d="fn.assign="+this.generateFunction("assign","s,v,l");c=zd(a.body);b.stage="inputs";q(c,function(a,c){var d="fn"+c;b.state[d]={vars:[],body:[],own:{}};b.state.computing= +d;var h=b.nextId();b.recurse(a,h);b.return_(h);b.state.inputs.push(d);a.watchId=c});this.state.computing="fn";this.stage="main";this.recurse(a);d='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+d+this.watchFns()+"return fn;";d=(new Function("$filter","getStringValue","ifDefined","plus",d))(this.$filter,ug,vg,yd);this.state=this.stage=void 0;d.literal=Cd(a);d.constant=a.constant;return d},USE:"use",STRICT:"strict",watchFns:function(){var a=[], +b=this.state.inputs,d=this;q(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;q(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+ +";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,f,e){var g,h,k=this,l,m,n;c=c||w;if(!e&&v(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,f,!0));else switch(a.type){case s.Program:q(a.body,function(b,c){k.recurse(b.expression,void 0,void 0,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(b||m); +break;case s.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);c(m);break;case s.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left, +b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){f&& +1!==f&&k.if_(k.isNull(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));c(b);break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,void 0,function(){k.if_(k.notNull(g),function(){a.computed?(h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),f&&1!==f&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g, +h),"{}")),m=k.computedMember(g,h),k.assign(b,m),d&&(d.computed=!0,d.name=h)):(f&&1!==f&&k.if_(k.isNull(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}")),m=k.nonComputedMember(g,a.property.name),k.assign(b,m),d&&(d.computed=!1,d.name=a.property.name))},function(){k.assign(b,"undefined")});c(b)},!!f);break;case s.CallExpression:b=b||this.nextId();a.filter?(h=k.filter(a.callee.name),l=[],q(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}), +m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){q(a.arguments,function(b){k.recurse(b,a.constant?void 0:k.nextId(),void 0,function(a){l.push(a)})});m=g.name?k.member(g.context,g.name,g.computed)+"("+l.join(",")+")":h+"("+l.join(",")+")";k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h=this.nextId();g={};this.recurse(a.left,void 0,g,function(){k.if_(k.notNull(g.context), +function(){k.recurse(a.right,h);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=[];q(a.elements,function(b){k.recurse(b,a.constant?void 0:k.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(b||m);break;case s.ObjectExpression:l=[];n=!1;q(a.properties,function(a){a.computed&&(n=!0)});n?(b=b||this.nextId(),this.assign(b,"{}"),q(a.properties,function(a){a.computed?(g=k.nextId(),k.recurse(a.key,g)):g=a.key.type=== +s.Identifier?a.key.name:""+a.key.value;h=k.nextId();k.recurse(a.value,h);k.assign(k.member(b,g,a.computed),h)})):(q(a.properties,function(b){k.recurse(b.value,a.constant?void 0:k.nextId(),void 0,function(a){l.push(k.escape(b.key.type===s.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case s.ThisExpression:this.assign(b,"s");c(b||"s");break;case s.LocalsExpression:this.assign(b,"l");c(b||"l");break;case s.NGValueParameter:this.assign(b,"v"),c(b|| +"v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ", +a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),d(),c.push("}"))}},not:function(a){return"!("+a+")"},isNull:function(a){return a+"==null"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a, +b):this.nonComputedMember(a,b)},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},lazyRecurse:function(a,b,d,c,f,e){var g=this;return function(){g.recurse(a,b,d,c,f,e)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(E(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(Y(a))return a.toString(); +if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw Ua("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};Ed.prototype={compile:function(a){var b=this;a=this.astBuilder.ast(a);V(a,b.$filter);var d,c;if(d=Bd(a))c=this.recurse(d);d=zd(a.body);var f;d&&(f=[],q(d,function(a,c){var d=b.recurse(a);a.input=d;f.push(d); +a.watchId=c}));var e=[];q(a.body,function(a){e.push(b.recurse(a.expression))});d=0===a.body.length?w:1===a.body.length?e[0]:function(a,b){var c;q(e,function(d){c=d(a,b)});return c};c&&(d.assign=function(a,b,d){return c(a,d,b)});f&&(d.inputs=f);d.literal=Cd(a);d.constant=a.constant;return d},recurse:function(a,b,d){var c,f,e=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,b);case s.UnaryExpression:return f=this.recurse(a.argument),this["unary"+ +a.operator](f,b);case s.BinaryExpression:return c=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](c,f,b);case s.LogicalExpression:return c=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](c,f,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return e.identifier(a.name,b,d);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(f=a.property.name), +a.computed&&(f=this.recurse(a.property)),a.computed?this.computedMember(c,f,b,d):this.nonComputedMember(c,f,b,d);case s.CallExpression:return g=[],q(a.arguments,function(a){g.push(e.recurse(a))}),a.filter&&(f=this.$filter(a.callee.name)),a.filter||(f=this.recurse(a.callee,!0)),a.filter?function(a,c,d,e){for(var n=[],p=0;p":function(a,b,d){return function(c,f,e,g){c=a(c,f,e,g)>b(c,f,e,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,f,e,g){c=a(c,f,e, +g)<=b(c,f,e,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,f,e,g){c=a(c,f,e,g)>=b(c,f,e,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,f,e,g){c=a(c,f,e,g)&&b(c,f,e,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,f,e,g){c=a(c,f,e,g)||b(c,f,e,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(f,e,g,h){f=a(f,e,g,h)?b(f,e,g,h):d(f,e,g,h);return c?{value:f}:f}},value:function(a,b){return function(){return b?{context:void 0, +name:void 0,value:a}:a}},identifier:function(a,b,d){return function(c,f,e,g){c=f&&a in f?f:c;d&&1!==d&&c&&null==c[a]&&(c[a]={});f=c?c[a]:void 0;return b?{context:c,name:a,value:f}:f}},computedMember:function(a,b,d,c){return function(f,e,g,h){var k=a(f,e,g,h),l,m;null!=k&&(l=b(f,e,g,h),l+="",c&&1!==c&&k&&!k[l]&&(k[l]={}),m=k[l]);return d?{context:k,name:l,value:m}:m}},nonComputedMember:function(a,b,d,c){return function(f,e,g,h){f=a(f,e,g,h);c&&1!==c&&f&&null==f[b]&&(f[b]={});e=null!=f?f[b]:void 0; +return d?{context:f,name:b,value:e}:e}},inputs:function(a,b){return function(d,c,f,e){return e?e[b]:a(d,c,f)}}};var rc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(a,d);this.astCompiler=d.csp?new Ed(this.ast,b):new Dd(this.ast,b)};rc.prototype={constructor:rc,parse:function(a){return this.astCompiler.compile(a)}};var ua=M("$sce"),pa={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},sc=/_([a-z])/g,yg=M("$compile"),ca=z.document.createElement("a"),Id= +Da(z.location.href);Jd.$inject=["$document"];Xc.$inject=["$provide"];var Qd=22,Pd=".",uc="0";Kd.$inject=["$locale"];Md.$inject=["$locale"];var Jg={yyyy:aa("FullYear",4,0,!1,!0),yy:aa("FullYear",2,0,!0,!0),y:aa("FullYear",1,0,!1,!0),MMMM:nb("Month"),MMM:nb("Month",!0),MM:aa("Month",2,1),M:aa("Month",1,1),LLLL:nb("Month",!1,!0),dd:aa("Date",2),d:aa("Date",1),HH:aa("Hours",2),H:aa("Hours",1),hh:aa("Hours",2,-12),h:aa("Hours",1,-12),mm:aa("Minutes",2),m:aa("Minutes",1),ss:aa("Seconds",2),s:aa("Seconds", +1),sss:aa("Milliseconds",3),EEEE:nb("Day"),EEE:nb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Kb(Math[0=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},Ig=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,Hg=/^-?\d+$/;Ld.$inject=["$locale"];var Cg=ma(P),Dg=ma(vb);Nd.$inject= +["$parse"];var Ae=ma({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var f="[object SVGAnimatedString]"===na.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(f)||a.preventDefault()})}}}}),wb={};q(Gb,function(a,b){function d(a,d,f){a.$watch(f[c],function(a){f.$set(b,!!a)})}if("multiple"!==a){var c=Ca("ng-"+b),f=d;"checked"===a&&(f=function(a,b,f){f.ngModel!==f[c]&&d(a,b,f)});wb[c]=function(){return{restrict:"A", +priority:100,link:f}}}});q(ld,function(a,b){wb[b]=function(){return{priority:100,link:function(a,c,f){if("ngPattern"===b&&"/"===f.ngPattern.charAt(0)&&(c=f.ngPattern.match(Ng))){f.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(f[b],function(a){f.$set(b,a)})}}}});q(["src","srcset","href"],function(a){var b=Ca("ng-"+a);wb[b]=function(){return{priority:99,link:function(d,c,f){var e=a,g=a;"href"===a&&"[object SVGAnimatedString]"===na.call(c.prop("href"))&&(g="xlinkHref",f.$attr[g]="xlink:href", +e=null);f.$observe(b,function(b){b?(f.$set(g,b),La&&e&&c.prop(e,f[g])):"href"===a&&f.$set(g,null)})}}}});var Mb={$addControl:w,$$renameControl:function(a,b){a.$name=b},$removeControl:w,$setValidity:w,$setDirty:w,$setPristine:w,$setSubmitted:w};Lb.$inject=["$element","$attrs","$scope","$animate","$interpolate"];Lb.prototype={$rollbackViewValue:function(){q(this.$$controls,function(a){a.$rollbackViewValue()})},$commitViewValue:function(){q(this.$$controls,function(a){a.$commitViewValue()})},$addControl:function(a){Pa(a.$name, +"input");this.$$controls.push(a);a.$name&&(this[a.$name]=a);a.$$parentForm=this},$$renameControl:function(a,b){var d=a.$name;this[d]===a&&delete this[d];this[b]=a;a.$name=b},$removeControl:function(a){a.$name&&this[a.$name]===a&&delete this[a.$name];q(this.$pending,function(b,d){this.$setValidity(d,null,a)},this);q(this.$error,function(b,d){this.$setValidity(d,null,a)},this);q(this.$$success,function(b,d){this.$setValidity(d,null,a)},this);$a(this.$$controls,a);a.$$parentForm=Mb},$setDirty:function(){this.$$animate.removeClass(this.$$element, +Va);this.$$animate.addClass(this.$$element,Rb);this.$dirty=!0;this.$pristine=!1;this.$$parentForm.$setDirty()},$setPristine:function(){this.$$animate.setClass(this.$$element,Va,Rb+" ng-submitted");this.$dirty=!1;this.$pristine=!0;this.$submitted=!1;q(this.$$controls,function(a){a.$setPristine()})},$setUntouched:function(){q(this.$$controls,function(a){a.$setUntouched()})},$setSubmitted:function(){this.$$animate.addClass(this.$$element,"ng-submitted");this.$submitted=!0;this.$$parentForm.$setSubmitted()}}; +Vd({clazz:Lb,set:function(a,b,d){var c=a[b];c?-1===c.indexOf(d)&&c.push(d):a[b]=[d]},unset:function(a,b,d){var c=a[b];c&&($a(c,d),0===c.length&&delete a[b])}});var ce=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||w}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Lb,compile:function(d,e){d.addClass(Va).addClass(ob);var g=e.name?"name":a&&e.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in +e)){var p=function(b){a.$apply(function(){n.$commitViewValue();n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",p);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",p)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var r=g?c(n.$name):w;g&&(r(a,n),e.$observe(g,function(b){n.$name!==b&&(r(a,void 0),n.$$parentForm.$$renameControl(n,b),r=c(n.$name),r(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n);r(a,void 0);R(n,Mb)})}}}}}]},Be=ce(), +Ne=ce(!0),Kg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,Vg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Wg=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,Lg=/^\s*(-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,de=/^(\d{4,})-(\d{2})-(\d{2})$/,ee=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/, +Cc=/^(\d{4,})-W(\d\d)$/,fe=/^(\d{4,})-(\d\d)$/,ge=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Xd=W();q(["date","datetime-local","month","time","week"],function(a){Xd[a]=!0});var he={text:function(a,b,d,c,f,e){Sa(a,b,d,c,f,e);xc(c)},date:pb("date",de,Nb(de,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":pb("datetimelocal",ee,Nb(ee,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:pb("time",ge,Nb(ge,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:pb("week",Cc,function(a,b){if(fa(a))return a; +if(E(a)){Cc.lastIndex=0;var d=Cc.exec(a);if(d){var c=+d[1],f=+d[2],e=d=0,g=0,h=0,k=Rd(c),f=7*(f-1);b&&(d=b.getHours(),e=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+f,d,e,g,h)}}return NaN},"yyyy-Www"),month:pb("month",fe,Nb(fe,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,f,e){yc(a,b,d,c);Yd(c);Sa(a,b,d,c,f,e);var g,h;if(v(d.min)||d.ngMin)c.$validators.min=function(a){return c.$isEmpty(a)||x(g)||a>=g},d.$observe("min",function(a){g=Ta(a);c.$validate()}); +if(v(d.max)||d.ngMax)c.$validators.max=function(a){return c.$isEmpty(a)||x(h)||a<=h},d.$observe("max",function(a){h=Ta(a);c.$validate()});if(v(d.step)||d.ngStep){var k;c.$validators.step=function(a,b){return c.$isEmpty(b)||x(k)||Zd(b,g||0,k)};d.$observe("step",function(a){k=Ta(a);c.$validate()})}},url:function(a,b,d,c,f,e){Sa(a,b,d,c,f,e);xc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||Vg.test(d)}},email:function(a,b,d,c,f,e){Sa(a,b,d,c,f,e);xc(c);c.$$parserName= +"email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Wg.test(d)}},radio:function(a,b,d,c){var f=!d.ngTrim||"false"!==S(d.ngTrim);x(d.name)&&b.attr("name",++rb);b.on("click",function(a){var g;b[0].checked&&(g=d.value,f&&(g=S(g)),c.$setViewValue(g,a&&a.type))});c.$render=function(){var a=d.value;f&&(a=S(a));b[0].checked=a===c.$viewValue};d.$observe("value",c.$render)},range:function(a,b,d,c,f,e){function g(a,c){b.attr(a,d[a]);d.$observe(a,c)}function h(a){n=Ta(a);ga(c.$modelValue)|| +(m?(a=b.val(),n>a&&(a=n,b.val(a)),c.$setViewValue(a)):c.$validate())}function k(a){p=Ta(a);ga(c.$modelValue)||(m?(a=b.val(),p=n},g("min",h));f&&(c.$validators.max=m?function(){return!0}:function(a,b){return c.$isEmpty(b)||x(p)||b<=p},g("max",k));e&&(c.$validators.step=m?function(){return!q.stepMismatch}:function(a,b){return c.$isEmpty(b)||x(r)||Zd(b,n||0,r)},g("step",l))},checkbox:function(a,b,d,c,f,e,g,h){var k=$d(h,a,"ngTrueValue",d.ngTrueValue,!0),l=$d(h,a,"ngFalseValue", +d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return qa(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:w,button:w,submit:w,reset:w,file:w},Rc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,e,g,h){h[0]&&(he[P(g.type)]||he.text)(f,e,g,h[0],b,a,d,c)}}}}],Xg=/^(true|false|\d+)$/, +ef=function(){return{restrict:"A",priority:100,compile:function(a,b){return Xg.test(b.ngValue)?function(a,b,f){a=a.$eval(f.ngValue);b.prop("value",a);f.$set("value",a)}:function(a,b,f){a.$watch(f.ngValue,function(a){b.prop("value",a);f.$set("value",a)})}}}},Fe=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,f){a.$$addBindingInfo(c,f.ngBind);c=c[0];b.$watch(f.ngBind,function(a){c.textContent=Yb(a)})}}}}],He=["$interpolate","$compile",function(a, +b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];e.$observe("ngBindTemplate",function(a){d.textContent=x(a)?"":a})}}}}],Ge=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,f){var e=b(f.ngBindHtml),g=b(f.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);return function(b,c,f){d.$$addBindingInfo(c,f.ngBindHtml);b.$watch(g,function(){var d=e(b); +c.html(a.getTrustedHtml(d)||"")})}}}}],df=ma({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),Ie=Ac("",!0),Ke=Ac("Odd",0),Je=Ac("Even",1),Le=Ra({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Me=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Wc={},Yg={blur:!0,focus:!0};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "), +function(a){var b=Ca("ng-"+a);Wc[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(f,e){var g=d(e[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Yg[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Pe=["$animate","$compile",function(a,b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,f,e,g){var h,k,l;d.$watch(f.ngIf,function(d){d?k||g(function(d,e){k=e;d[d.length++]= +b.$$createComment("end ngIf",f.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=ub(h.clone),a.leave(l).done(function(a){!1!==a&&(l=null)}),h=null))})}}}],Qe=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:$.noop,compile:function(c,f){var e=f.ngInclude||f.src,g=f.onload||"",h=f.autoscroll;return function(c,f,m,n,p){var q=0,s,w,u,H=function(){w&&(w.remove(), +w=null);s&&(s.$destroy(),s=null);u&&(d.leave(u).done(function(a){!1!==a&&(w=null)}),w=u,u=null)};c.$watch(e,function(e){var m=function(a){!1===a||!v(h)||h&&!c.$eval(h)||b()},w=++q;e?(a(e,!0).then(function(a){if(!c.$$destroyed&&w===q){var b=c.$new();n.template=a;a=p(b,function(a){H();d.enter(a,null,f).done(m)});s=b;u=a;s.$emit("$includeContentLoaded",e);c.$eval(g)}},function(){c.$$destroyed||w!==q||(H(),c.$emit("$includeContentError",e))}),c.$emit("$includeContentRequested",e)):(H(),n.template=null)})}}}}], +gf=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,f){na.call(d[0]).match(/SVG/)?(d.empty(),a(Zc(f.template,z.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(f.template),a(d.contents())(b))}}}],Re=Ra({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),cf=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var f=d.ngList||", ",e="false"!==d.ngTrim,g= +e?S(f):f;c.$parsers.push(function(a){if(!x(a)){var b=[];a&&q(a.split(g),function(a){a&&b.push(e?S(a):a)});return b}});c.$formatters.push(function(a){if(C(a))return a.join(f)});c.$isEmpty=function(a){return!a||!a.length}}}},ob="ng-valid",Ud="ng-invalid",Va="ng-pristine",Rb="ng-dirty",qb=M("ngModel");Ob.$inject="$scope $exceptionHandler $attrs $element $parse $animate $timeout $q $interpolate".split(" ");Ob.prototype={$$initGetterSetters:function(){if(this.$options.getOption("getterSetter")){var a= +this.$$parse(this.$$attr.ngModel+"()"),b=this.$$parse(this.$$attr.ngModel+"($$$p)");this.$$ngModelGet=function(b){var c=this.$$parsedNgModel(b);y(c)&&(c=a(b));return c};this.$$ngModelSet=function(a,c){y(this.$$parsedNgModel(a))?b(a,{$$$p:c}):this.$$parsedNgModelAssign(a,c)}}else if(!this.$$parsedNgModel.assign)throw qb("nonassign",this.$$attr.ngModel,ya(this.$$element));},$render:w,$isEmpty:function(a){return x(a)||""===a||null===a||a!==a},$$updateEmptyClasses:function(a){this.$isEmpty(a)?(this.$$animate.removeClass(this.$$element, +"ng-not-empty"),this.$$animate.addClass(this.$$element,"ng-empty")):(this.$$animate.removeClass(this.$$element,"ng-empty"),this.$$animate.addClass(this.$$element,"ng-not-empty"))},$setPristine:function(){this.$dirty=!1;this.$pristine=!0;this.$$animate.removeClass(this.$$element,Rb);this.$$animate.addClass(this.$$element,Va)},$setDirty:function(){this.$dirty=!0;this.$pristine=!1;this.$$animate.removeClass(this.$$element,Va);this.$$animate.addClass(this.$$element,Rb);this.$$parentForm.$setDirty()}, +$setUntouched:function(){this.$touched=!1;this.$untouched=!0;this.$$animate.setClass(this.$$element,"ng-untouched","ng-touched")},$setTouched:function(){this.$touched=!0;this.$untouched=!1;this.$$animate.setClass(this.$$element,"ng-touched","ng-untouched")},$rollbackViewValue:function(){this.$$timeout.cancel(this.$$pendingDebounce);this.$viewValue=this.$$lastCommittedViewValue;this.$render()},$validate:function(){if(!ga(this.$modelValue)){var a=this.$$lastCommittedViewValue,b=this.$$rawModelValue, +d=this.$valid,c=this.$modelValue,f=this.$options.getOption("allowInvalid"),e=this;this.$$runValidators(b,a,function(a){f||d===a||(e.$modelValue=a?b:void 0,e.$modelValue!==c&&e.$$writeModelToScope())})}},$$runValidators:function(a,b,d){function c(){var c=!0;q(k.$validators,function(d,f){var g=Boolean(d(a,b));c=c&&g;e(f,g)});return c?!0:(q(k.$asyncValidators,function(a,b){e(b,null)}),!1)}function f(){var c=[],d=!0;q(k.$asyncValidators,function(f,g){var h=f(a,b);if(!h||!y(h.then))throw qb("nopromise", +h);e(g,void 0);c.push(h.then(function(){e(g,!0)},function(){d=!1;e(g,!1)}))});c.length?k.$$q.all(c).then(function(){g(d)},w):g(!0)}function e(a,b){h===k.$$currentValidationRunId&&k.$setValidity(a,b)}function g(a){h===k.$$currentValidationRunId&&d(a)}this.$$currentValidationRunId++;var h=this.$$currentValidationRunId,k=this;(function(){var a=k.$$parserName||"parse";if(x(k.$$parserValid))e(a,null);else return k.$$parserValid||(q(k.$validators,function(a,b){e(b,null)}),q(k.$asyncValidators,function(a, +b){e(b,null)})),e(a,k.$$parserValid),k.$$parserValid;return!0})()?c()?f():g(!1):g(!1)},$commitViewValue:function(){var a=this.$viewValue;this.$$timeout.cancel(this.$$pendingDebounce);if(this.$$lastCommittedViewValue!==a||""===a&&this.$$hasNativeValidators)this.$$updateEmptyClasses(a),this.$$lastCommittedViewValue=a,this.$pristine&&this.$setDirty(),this.$$parseAndValidate()},$$parseAndValidate:function(){var a=this.$$lastCommittedViewValue,b=this;if(this.$$parserValid=x(a)?void 0:!0)for(var d=0;d< +this.$parsers.length;d++)if(a=this.$parsers[d](a),x(a)){this.$$parserValid=!1;break}ga(this.$modelValue)&&(this.$modelValue=this.$$ngModelGet(this.$$scope));var c=this.$modelValue,f=this.$options.getOption("allowInvalid");this.$$rawModelValue=a;f&&(this.$modelValue=a,b.$modelValue!==c&&b.$$writeModelToScope());this.$$runValidators(a,this.$$lastCommittedViewValue,function(d){f||(b.$modelValue=d?a:void 0,b.$modelValue!==c&&b.$$writeModelToScope())})},$$writeModelToScope:function(){this.$$ngModelSet(this.$$scope, +this.$modelValue);q(this.$viewChangeListeners,function(a){try{a()}catch(b){this.$$exceptionHandler(b)}},this)},$setViewValue:function(a,b){this.$viewValue=a;this.$options.getOption("updateOnDefault")&&this.$$debounceViewValueCommit(b)},$$debounceViewValueCommit:function(a){var b=this.$options.getOption("debounce");Y(b[a])?b=b[a]:Y(b["default"])&&(b=b["default"]);this.$$timeout.cancel(this.$$pendingDebounce);var d=this;0 +f||c.$isEmpty(b)||b.length<=f}}}}},Uc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var f=0;d.$observe("minlength",function(a){f=Z(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=f}}}}};z.angular.bootstrap?z.console&&console.log("WARNING: Tried to load angular more than once."):(ue(),xe($),$.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale", +{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "), +WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a, +c){var f=a|0,e=c;void 0===e&&(e=Math.min(b(a),3));Math.pow(10,e);return 1==f&&0==e?"one":"other"}})}]),D(function(){pe(z.document,Mc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); +//# sourceMappingURL=angular.min.js.map diff --git a/my-todolist/settings.js b/my-todolist/settings.js index c6ac9da..9b4bbcd 100644 --- a/my-todolist/settings.js +++ b/my-todolist/settings.js @@ -3,5 +3,5 @@ exports.dbConfig = { password: "Lanetteam1", server: "mongodb://localhost", database: "test", - port:27017 + port: 27017 }; \ No newline at end of file diff --git a/my-todolist/todo.css b/my-todolist/todo.css new file mode 100644 index 0000000..5e941f9 --- /dev/null +++ b/my-todolist/todo.css @@ -0,0 +1,4 @@ +.done-true { + text-decoration: line-through; + color: grey; +} \ No newline at end of file diff --git a/my-todolist/todo.js b/my-todolist/todo.js new file mode 100644 index 0000000..1d73c2d --- /dev/null +++ b/my-todolist/todo.js @@ -0,0 +1,28 @@ +angular.module('todoApp', []) + .controller('TodoListController', function() { + var todoList = this; + todoList.todos = [ + {text:'learn angular', done:true}, + {text:'build an angular app', done:false}]; + + todoList.addTodo = function() { + todoList.todos.push({text:todoList.todoText, done:false}); + todoList.todoText = ''; + }; + + todoList.remaining = function() { + var count = 0; + angular.forEach(todoList.todos, function(todo) { + count += todo.done ? 0 : 1; + }); + return count; + }; + + todoList.archive = function() { + var oldTodos = todoList.todos; + todoList.todos = []; + angular.forEach(oldTodos, function(todo) { + if (!todo.done) todoList.todos.push(todo); + }); + }; + }); \ No newline at end of file