348 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | ||
|  * @license
 | ||
|  * Copyright Google Inc. All Rights Reserved.
 | ||
|  *
 | ||
|  * Use of this source code is governed by an MIT-style license that can be
 | ||
|  * found in the LICENSE file at https://angular.io/license
 | ||
|  */
 | ||
| 
 | ||
| (function () {
 | ||
| 
 | ||
|   'use strict';
 | ||
| 
 | ||
|   // keep a reference to compileProvider so we can register new component-directives
 | ||
|   // on-the-fly based on $routeProvider configuration
 | ||
|   // TODO: remove this– right now you can only bootstrap one Angular app with this hack
 | ||
|   var $compileProvider, $q, $injector;
 | ||
| 
 | ||
|   /**
 | ||
|    * This module loads services that mimic ngRoute's configuration, and includes
 | ||
|    * an anchor link directive that intercepts clicks to routing.
 | ||
|    *
 | ||
|    * This module is intended to be used as a stop-gap solution for projects upgrading from ngRoute.
 | ||
|    * It intentionally does not implement all features of ngRoute.
 | ||
|    */
 | ||
|   angular.module('ngRouteShim', [])
 | ||
|     .provider('$route', $RouteProvider)
 | ||
|     .config(['$compileProvider', function (compileProvider) {
 | ||
|       $compileProvider = compileProvider;
 | ||
|     }])
 | ||
|     .factory('$routeParams', $routeParamsFactory)
 | ||
|     .directive('a', anchorLinkDirective)
 | ||
| 
 | ||
|     // Connects the legacy $routeProvider config shim to Component Router's config.
 | ||
|     .run(['$route', '$rootRouter', function ($route, $rootRouter) {
 | ||
|       $route.$$subscribe(function (routeDefinition) {
 | ||
|         if (!angular.isArray(routeDefinition)) {
 | ||
|           routeDefinition = [routeDefinition];
 | ||
|         }
 | ||
|         $rootRouter.config(routeDefinition);
 | ||
|       });
 | ||
|     }]);
 | ||
| 
 | ||
| 
 | ||
|   /**
 | ||
|    * A shimmed out provider that provides the same API as ngRoute's $routeProvider, but uses these calls
 | ||
|    * to configure Component Router.
 | ||
|    */
 | ||
|   function $RouteProvider() {
 | ||
| 
 | ||
|     var routes = [];
 | ||
|     var subscriptionFn = null;
 | ||
| 
 | ||
|     var routeMap = {};
 | ||
| 
 | ||
|     // Stats for which routes are skipped
 | ||
|     var skipCount = 0;
 | ||
|     var successCount = 0;
 | ||
|     var allCount = 0;
 | ||
| 
 | ||
|     function consoleMetrics() {
 | ||
|       return '(' + skipCount + ' skipped / ' + successCount + ' success / ' + allCount + ' total)';
 | ||
|     }
 | ||
| 
 | ||
| 
 | ||
|     /**
 | ||
|      * @ngdoc method
 | ||
|      * @name $routeProvider#when
 | ||
|      *
 | ||
|      * @param {string} path Route path (matched against `$location.path`). If `$location.path`
 | ||
|      *    contains redundant trailing slash or is missing one, the route will still match and the
 | ||
|      *    `$location.path` will be updated to add or drop the trailing slash to exactly match the
 | ||
|      *    route definition.
 | ||
|      *
 | ||
|      * @param {Object} route Mapping information to be assigned to `$route.current` on route
 | ||
|      *    match.
 | ||
|      */
 | ||
|     this.when = function(path, route) {
 | ||
|       //copy original route object to preserve params inherited from proto chain
 | ||
|       var routeCopy = angular.copy(route);
 | ||
| 
 | ||
|       allCount++;
 | ||
| 
 | ||
|       if (angular.isDefined(routeCopy.reloadOnSearch)) {
 | ||
|         console.warn('Route for "' + path + '" uses "reloadOnSearch" which is not implemented.');
 | ||
|       }
 | ||
|       if (angular.isDefined(routeCopy.caseInsensitiveMatch)) {
 | ||
|         console.warn('Route for "' + path + '" uses "caseInsensitiveMatch" which is not implemented.');
 | ||
|       }
 | ||
| 
 | ||
|       // use new wildcard format
 | ||
|       path = reformatWildcardParams(path);
 | ||
| 
 | ||
|       if (path[path.length - 1] == '*') {
 | ||
|         skipCount++;
 | ||
|         console.warn('Route for "' + path + '" ignored because it ends with *. Skipping.', consoleMetrics());
 | ||
|         return this;
 | ||
|       }
 | ||
| 
 | ||
|       if (path.indexOf('?') > -1) {
 | ||
|         skipCount++;
 | ||
|         console.warn('Route for "' + path + '" ignored because it has optional parameters. Skipping.', consoleMetrics());
 | ||
|         return this;
 | ||
|       }
 | ||
| 
 | ||
|       if (typeof route.redirectTo == 'function') {
 | ||
|         skipCount++;
 | ||
|         console.warn('Route for "' + path + '" ignored because lazy redirecting to a function is not yet implemented. Skipping.', consoleMetrics());
 | ||
|         return this;
 | ||
|       }
 | ||
| 
 | ||
| 
 | ||
|       var routeDefinition = {
 | ||
|         path: path,
 | ||
|         data: routeCopy
 | ||
|       };
 | ||
| 
 | ||
|       routeMap[path] = routeCopy;
 | ||
| 
 | ||
|       if (route.redirectTo) {
 | ||
|         routeDefinition.redirectTo = [routeMap[route.redirectTo].name];
 | ||
|       } else {
 | ||
|         if (routeCopy.controller && !routeCopy.controllerAs) {
 | ||
|           console.warn('Route for "' + path + '" should use "controllerAs".');
 | ||
|         }
 | ||
| 
 | ||
|         var componentName = routeObjToRouteName(routeCopy, path);
 | ||
| 
 | ||
|         if (!componentName) {
 | ||
|           throw new Error('Could not determine a name for route "' + path + '".');
 | ||
|         }
 | ||
| 
 | ||
|         routeDefinition.component = componentName;
 | ||
|         routeDefinition.name = route.name || upperCase(componentName);
 | ||
| 
 | ||
|         var directiveController = routeCopy.controller;
 | ||
| 
 | ||
|         var componentDefinition = {
 | ||
|           controller: directiveController,
 | ||
|           controllerAs: routeCopy.controllerAs
 | ||
| 
 | ||
|         };
 | ||
|         if (routeCopy.templateUrl) componentDefinition.templateUrl = routeCopy.templateUrl;
 | ||
|         if (routeCopy.template) componentDefinition.template = routeCopy.template;
 | ||
| 
 | ||
| 
 | ||
|         // if we have route resolve options, prepare a wrapper controller
 | ||
|         if (directiveController && routeCopy.resolve) {
 | ||
|           var originalController = directiveController;
 | ||
|           var resolvedLocals = {};
 | ||
| 
 | ||
|           componentDefinition.controller = ['$injector', '$scope', function ($injector, $scope) {
 | ||
|             var locals = angular.extend({
 | ||
|               $scope: $scope
 | ||
|             }, resolvedLocals);
 | ||
| 
 | ||
|             return $injector.instantiate(originalController, locals);
 | ||
|           }];
 | ||
| 
 | ||
|           // we resolve the locals in a canActivate block
 | ||
|           componentDefinition.controller.$canActivate = function() {
 | ||
|             var locals = angular.extend({}, routeCopy.resolve);
 | ||
| 
 | ||
|             angular.forEach(locals, function(value, key) {
 | ||
|               locals[key] = angular.isString(value) ?
 | ||
|                 $injector.get(value) : $injector.invoke(value, null, null, key);
 | ||
|             });
 | ||
| 
 | ||
|             return $q.all(locals).then(function (locals) {
 | ||
|               resolvedLocals = locals;
 | ||
|             }).then(function () {
 | ||
|               return true;
 | ||
|             });
 | ||
|           };
 | ||
|         }
 | ||
| 
 | ||
|         // register the dynamically created directive
 | ||
|         $compileProvider.component(componentName, componentDefinition);
 | ||
|       }
 | ||
|       if (subscriptionFn) {
 | ||
|         subscriptionFn(routeDefinition);
 | ||
|       } else {
 | ||
|         routes.push(routeDefinition);
 | ||
|       }
 | ||
|       successCount++;
 | ||
| 
 | ||
|       return this;
 | ||
|     };
 | ||
| 
 | ||
|     this.otherwise = function(params) {
 | ||
|       if (typeof params === 'string') {
 | ||
|         params = {redirectTo: params};
 | ||
|       }
 | ||
|       this.when('/*rest', params);
 | ||
|       return this;
 | ||
|     };
 | ||
| 
 | ||
| 
 | ||
|     this.$get = ['$q', '$injector', function (q, injector) {
 | ||
|       $q = q;
 | ||
|       $injector = injector;
 | ||
| 
 | ||
|       var $route = {
 | ||
|         routes: routeMap,
 | ||
| 
 | ||
|         /**
 | ||
|          * @ngdoc method
 | ||
|          * @name $route#reload
 | ||
|          *
 | ||
|          * @description
 | ||
|          * Causes `$route` service to reload the current route even if
 | ||
|          * {@link ng.$location $location} hasn't changed.
 | ||
|          */
 | ||
|         reload: function() {
 | ||
|           throw new Error('Not implemented: $route.reload');
 | ||
|         },
 | ||
| 
 | ||
|         /**
 | ||
|          * @ngdoc method
 | ||
|          * @name $route#updateParams
 | ||
|          */
 | ||
|         updateParams: function(newParams) {
 | ||
|           throw new Error('Not implemented: $route.updateParams');
 | ||
|         },
 | ||
| 
 | ||
|         /**
 | ||
|          * Runs the given `fn` whenever new configs are added.
 | ||
|          * Only one subscription is allowed.
 | ||
|          * Passed `fn` is called synchronously.
 | ||
|          */
 | ||
|         $$subscribe: function(fn) {
 | ||
|           if (subscriptionFn) {
 | ||
|             throw new Error('only one subscription allowed');
 | ||
|           }
 | ||
|           subscriptionFn = fn;
 | ||
|           subscriptionFn(routes);
 | ||
|           routes = [];
 | ||
|         },
 | ||
| 
 | ||
|         /**
 | ||
|          * Runs a string with stats about many route configs were adapted, and how many were
 | ||
|          * dropped because they are incompatible.
 | ||
|          */
 | ||
|         $$getStats: consoleMetrics
 | ||
|       };
 | ||
| 
 | ||
|       return $route;
 | ||
|     }];
 | ||
| 
 | ||
|   }
 | ||
| 
 | ||
|   function $routeParamsFactory($rootRouter, $rootScope) {
 | ||
|     // the identity of this object cannot change
 | ||
|     var paramsObj = {};
 | ||
| 
 | ||
|     $rootScope.$on('$routeChangeSuccess', function () {
 | ||
|       var newParams = $rootRouter.currentInstruction && $rootRouter.currentInstruction.component.params;
 | ||
| 
 | ||
|       angular.forEach(paramsObj, function (val, name) {
 | ||
|         delete paramsObj[name];
 | ||
|       });
 | ||
|       angular.forEach(newParams, function (val, name) {
 | ||
|         paramsObj[name] = val;
 | ||
|       });
 | ||
|     });
 | ||
| 
 | ||
|     return paramsObj;
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Allows normal anchor links to kick off routing.
 | ||
|    */
 | ||
|   function anchorLinkDirective($rootRouter) {
 | ||
|     return {
 | ||
|       restrict: 'E',
 | ||
|       link: function (scope, element) {
 | ||
|         // If the linked element is not an anchor tag anymore, do nothing
 | ||
|         if (element[0].nodeName.toLowerCase() !== 'a') {
 | ||
|           return;
 | ||
|         }
 | ||
| 
 | ||
|         // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
 | ||
|         var hrefAttrName = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
 | ||
|           'xlink:href' : 'href';
 | ||
| 
 | ||
|         element.on('click', function (event) {
 | ||
|           if (event.which !== 1) {
 | ||
|             return;
 | ||
|           }
 | ||
| 
 | ||
|           var href = element.attr(hrefAttrName);
 | ||
|           var target = element.attr('target');
 | ||
|           var isExternal = (['_blank', '_parent', '_self', '_top'].indexOf(target) > -1);
 | ||
| 
 | ||
|           if (href && $rootRouter.recognize(href) && !isExternal) {
 | ||
|             $rootRouter.navigateByUrl(href);
 | ||
|             event.preventDefault();
 | ||
|           }
 | ||
|         });
 | ||
|       }
 | ||
|     };
 | ||
|   }
 | ||
| 
 | ||
|   /**
 | ||
|    * Given a route object, attempts to find a unique directive name.
 | ||
|    *
 | ||
|    * @param route – route config object passed to $routeProvider.when
 | ||
|    * @param path – route configuration path
 | ||
|    * @returns {string|name} – a normalized (camelCase) directive name
 | ||
|    */
 | ||
|   function routeObjToRouteName(route, path) {
 | ||
|     var name = route.controllerAs;
 | ||
| 
 | ||
|     var controller = route.controller;
 | ||
|     if (!name && controller) {
 | ||
|       if (angular.isArray(controller)) {
 | ||
|         controller = controller[controller.length - 1];
 | ||
|       }
 | ||
|       name = controller.name;
 | ||
|     }
 | ||
| 
 | ||
|     if (!name) {
 | ||
|       var segments = path.split('/');
 | ||
|       name = segments[segments.length - 1];
 | ||
|     }
 | ||
| 
 | ||
|     if (name) {
 | ||
|       name = name + 'AutoCmp';
 | ||
|     }
 | ||
| 
 | ||
|     return name;
 | ||
|   }
 | ||
| 
 | ||
|   function upperCase(str) {
 | ||
|     return str.charAt(0).toUpperCase() + str.substr(1);
 | ||
|   }
 | ||
| 
 | ||
|   /*
 | ||
|    * Changes "/:foo*" to "/*foo"
 | ||
|    */
 | ||
|   var WILDCARD_PARAM_RE = new RegExp('\\/:([a-z0-9]+)\\*', 'gi');
 | ||
|   function reformatWildcardParams(path) {
 | ||
|     return path.replace(WILDCARD_PARAM_RE, function (m, m1) {
 | ||
|       return '/*' + m1;
 | ||
|     });
 | ||
|   }
 | ||
| 
 | ||
| }());
 |