2&&"[]"===a.slice(c-2)&&(u=!0,a=a.slice(0,c-2),r[a]||(r[a]=[])),o=i[1]?decodeURIComponent(i[1]):""),u?r[a].push(o):r[a]=o}return r},recognize:function(t){var e,r,n,o,i=[this.rootState],a={},s=!1;if(o=t.indexOf("?"),-1!==o){var l=t.substr(o+1,t.length);t=t.substr(0,o),a=this.parseQueryString(l)}for(t=decodeURI(t),"/"!==t.charAt(0)&&(t="/"+t),e=t.length,e>1&&"/"===t.charAt(e-1)&&(t=t.substr(0,e-1),s=!0),r=0,n=t.length;n>r&&(i=u(i,t.charAt(r)),i.length);r++);var f=[];for(r=0,n=i.length;n>r;r++)i[r].handlers&&f.push(i[r]);i=c(f);var h=f[0];return h&&h.handlers?(s&&"(.+)$"===h.regex.source.slice(-5)&&(t+="/"),p(h,t,a)):void 0}},d.prototype.map=f,d.VERSION="VERSION_STRING_PLACEHOLDER",d}()),f="/*childRoute",h=function(){this.rules={}};t(h,{config:function(t,e){"app"===t&&(t="/"),this.rules[t]||(this.rules[t]=new v(t)),this.rules[t].config(e)},recognize:function(t){var e=void 0!==arguments[1]?arguments[1]:"/",r=this;if("undefined"!=typeof t){var n=this.rules[e];if(n){var i=n.recognize(t);if(i){var a=i[i.length-1],c=a.handler,u=a.params,s={viewports:{},params:u};if(u&&u.childRoute){var p="/"+u.childRoute;s.canonicalUrl=c.rewroteUrl.substr(0,c.rewroteUrl.length-(u.childRoute.length+1)),o(c.components,function(t,e){s.viewports[e]=r.recognize(p,t)}),s.canonicalUrl+=s.viewports[Object.keys(s.viewports)[0]].canonicalUrl}else s.canonicalUrl=c.rewroteUrl,o(c.components,function(t,e){s.viewports[e]={viewports:{}}});return o(s.viewports,function(t,e){t.component=c.components[e],t.params=u}),s}}}},generate:function(t,e){var r,n="";do{if(r=null,o(this.rules,function(o){o.hasRoute(t)&&(n=o.generate(t,e)+n,r=o)}),!r)return"";t=r.name}while("/"!==r.name);return n}},{}),Object.defineProperty(h.prototype.recognize,"parameters",{get:function(){return[[$traceurRuntime.type.string],[]]}});var v=function(t){this.name=t,this.rewrites={},this.recognizer=new l};return t(v,{config:function(t){var e=this;t instanceof Array?t.forEach(function(t){return e.configOne(t)}):this.configOne(t)},getCanonicalUrl:function(t){return"."===t[0]&&(t=t.substr(1)),(""===t||"/"!==t[0])&&(t="/"+t),o(this.rewrites,function(e,r){"/"===r?"/"===t&&(t=e):0===t.indexOf(r)&&(t=t.replace(r,e))}),t},configOne:function(t){var e=this;if(t.redirectTo){if(this.rewrites[t.path])throw new Error('"'+t.path+'" already maps to "'+this.rewrites[t.path]+'"');return void(this.rewrites[t.path]=t.redirectTo)}if(t.component){if(t.components)throw new Error('A route config should have either a "component" or "components" property, but not both.');t.components=t.component,delete t.component}"string"==typeof t.components&&(t.components={"default":t.components});var r;t.as?r=[t.as]:(r=i(t.components,function(t,e){return e+":"+t}),t.components["default"]&&r.push(t.components["default"])),r.forEach(function(r){return e.recognizer.add([{path:t.path,handler:t}],{as:r})});var o=n(t);o.path+=f,this.recognizer.add([{path:o.path,handler:o}])},recognize:function(t){var e=this.getCanonicalUrl(t),r=this.recognizer.recognize(e);return r&&(r[0].handler.rewroteUrl=e),r},generate:function(t,e){return this.recognizer.generate(t,e)},hasRoute:function(t){return this.recognizer.hasRoute(t)}},{}),new h}]);
\ No newline at end of file
diff --git a/dist/router.js b/dist/router.js
index 870237b..ab5dfb8 100644
--- a/dist/router.js
+++ b/dist/router.js
@@ -129,15 +129,21 @@ define(["assert", './grammar', './pipeline'], function($__0,$__2,$__4) {
return this.registry.generate(name, params);
}
}, {});
- Router.parameters = [[Grammar], [Pipeline], [], []];
- Router.prototype.generate.parameters = [[$traceurRuntime.type.string], []];
+ Object.defineProperty(Router, "parameters", {get: function() {
+ return [[Grammar], [Pipeline], [], []];
+ }});
+ Object.defineProperty(Router.prototype.generate, "parameters", {get: function() {
+ return [[$traceurRuntime.type.string], []];
+ }});
var RootRouter = function RootRouter(grammar, pipeline) {
assert.argumentTypes(grammar, Grammar, pipeline, Pipeline);
$traceurRuntime.superCall(this, $RootRouter.prototype, "constructor", [grammar, pipeline, null, '/']);
};
var $RootRouter = RootRouter;
($traceurRuntime.createClass)(RootRouter, {}, {}, Router);
- RootRouter.parameters = [[Grammar], [Pipeline]];
+ Object.defineProperty(RootRouter, "parameters", {get: function() {
+ return [[Grammar], [Pipeline]];
+ }});
var ChildRouter = function ChildRouter(parent, name) {
$traceurRuntime.superCall(this, $ChildRouter.prototype, "constructor", [parent.registry, parent.pipeline, parent, name]);
this.parent = parent;
@@ -169,3 +175,5 @@ define(["assert", './grammar', './pipeline'], function($__0,$__2,$__4) {
__esModule: true
};
});
+
+//# sourceMappingURL=router.ats
diff --git a/src/router-directive.es5.js b/src/router-directive.es5.js
index e85180d..39e3259 100644
--- a/src/router-directive.es5.js
+++ b/src/router-directive.es5.js
@@ -12,6 +12,7 @@ angular.module('ngNewRouter', [])
.factory('$setupRoutersStep', setupRoutersStepFactory)
.factory('$initLocalsStep', initLocalsStepFactory)
.factory('$initControllersStep', initControllersStepFactory)
+ .factory('$bindRouteParamsForDirectiveStep', bindRouteParamsForDirectiveStepFactory)
.factory('$runCanDeactivateHookStep', runCanDeactivateHookStepFactory)
.factory('$runCanActivateHookStep', runCanActivateHookStepFactory)
.factory('$loadTemplatesStep', loadTemplatesStepFactory)
@@ -29,7 +30,9 @@ angular.module('ngNewRouter', [])
*/
angular.module('ng')
.provider('$controllerIntrospector', $controllerIntrospectorProvider)
- .config(controllerProviderDecorator);
+ .provider('$compileIntrospector', $compileIntrospectorProvider)
+ .config(controllerProviderDecorator)
+ .config(compileProviderDecorator);
/*
* decorates with routing info
@@ -42,6 +45,14 @@ function controllerProviderDecorator($controllerProvider, $controllerIntrospecto
};
}
+function compileProviderDecorator($compileProvider, $compileIntrospectorProvider) {
+ var directive = $compileProvider.directive;
+ $compileProvider.directive = function (name, directiveFactory) {
+ $compileIntrospectorProvider.directive(name, directiveFactory);
+ return directive.apply(this, arguments);
+ };
+}
+
/*
* private service that holds route mappings for each controller
*/
@@ -76,12 +87,50 @@ function $controllerIntrospectorProvider() {
}
}
-function routerFactory($$rootRouter, $rootScope, $location, $$grammar, $controllerIntrospector) {
+
+/*
+ * private service that holds route mappings for each controller
+ */
+function $compileIntrospectorProvider() {
+ var directiveFactories = [];
+ var onDirectiveDefined = null;
+ return {
+ directive: function (name, directiveFactory) {
+ if (angular.isArray(directiveFactory)) {
+ directiveFactory = directiveFactory[directiveFactory.length - 1];
+ }
+ if (directiveFactory.$routeConfig) {
+ if (onDirectiveDefined) {
+ onDirectiveDefined(name, directiveFactory.$routeConfig);
+ } else {
+ directiveFactories.push({name: name, config: directiveFactory.$routeConfig});
+ }
+ }
+ },
+ $get: ['$componentLoader', '$injector', function ($componentLoader, $injector) {
+ return function (newOnDirectiveDefined) {
+ onDirectiveDefined = function (name, config) {
+ return newOnDirectiveDefined(name, config);
+ };
+ while(directiveFactories.length > 0) {
+ var rule = directiveFactories.pop();
+ onDirectiveDefined(rule.name, rule.config);
+ }
+ }
+ }]
+ }
+}
+
+function routerFactory($$rootRouter, $rootScope, $location, $$grammar, $controllerIntrospector, $compileIntrospector) {
$controllerIntrospector(function (name, config) {
$$grammar.config(name, config);
});
+ $compileIntrospector(function (name, config) {
+ $$grammar.config(name, config);
+ });
+
$rootScope.$watch(function () {
return $location.path();
}, function (newUrl) {
@@ -187,7 +236,11 @@ function ngViewportDirective($animate, $injector, $q, $router) {
});
var newController = instruction.controller;
- newScope[componentName] = newController;
+ if (instruction.directive && instruction.directive.controllerAs) {
+ newScope[instruction.directive.controllerAs] = newController;
+ } else {
+ newScope[componentName] = newController;
+ }
var result;
if (currentController && currentController.deactivate) {
@@ -366,23 +419,56 @@ function initLocalsStepFactory() {
/*
* $initControllersStep
*/
-function initControllersStepFactory($controller, $componentLoader) {
+function initControllersStepFactory($controller, $componentLoader, $injector) {
return function initControllers(instruction) {
return instruction.router.traverseInstruction(instruction, function(instruction) {
var controllerName = $componentLoader.controllerName(instruction.component);
+ var directiveName = $componentLoader.directiveName(instruction.component);
var locals = instruction.locals;
var ctrl;
try {
ctrl = $controller(controllerName, locals);
} catch(e) {
- console.warn && console.warn('Could not instantiate controller', controllerName);
- ctrl = $controller(angular.noop, locals);
+ try {
+ var directives = $injector.get(directiveName);
+ // can't handle two names on the same directive yet
+ instruction.directive = directives[0];
+ ctrl = $controller(instruction.directive.controller, locals);
+ } catch(e) {
+ console.warn && console.warn('Could not instantiate controller', controllerName);
+ ctrl = $controller(angular.noop, locals);
+ }
}
return instruction.controller = ctrl;
});
}
}
+function bindRouteParamsForDirectiveStepFactory() {
+ return function bindRouteParamsForDirective(instruction) {
+ return instruction.router.traverseInstruction(instruction, function(instruction) {
+ if (instruction.directive && instruction.directive.bindToController) {
+ var bindings;
+ if (typeof instruction.directive.bindToController == 'object') {
+ // ng 1.4 object syntax
+ bindings = instruction.directive.$$bindings.bindToController;
+ } else if (instruction.directive.bindToController == true) {
+ // ng 1.3 syntax
+ bindings = instruction.directive.$$isolateBindings;
+ }
+ if (bindings) {
+ Object.keys(bindings).forEach(function(key) {
+ if (instruction.params[bindings[key].attrName]) {
+ instruction.controller[key] = instruction.params[bindings[key].attrName];
+ }
+ });
+ }
+ }
+ return true;
+ });
+ };
+}
+
function runCanDeactivateHookStepFactory() {
return function runCanDeactivateHook(instruction) {
return instruction.router.canDeactivatePorts(instruction);
@@ -408,7 +494,16 @@ function runCanActivateHookStepFactory($injector) {
function loadTemplatesStepFactory($componentLoader, $templateRequest) {
return function loadTemplates(instruction) {
return instruction.router.traverseInstruction(instruction, function(instruction) {
- var componentTemplateUrl = $componentLoader.template(instruction.component);
+ var componentTemplateUrl;
+ if (instruction.directive) {
+ if (instruction.directive.template) {
+ return instruction.template = instruction.directive.template
+ } else {
+ componentTemplateUrl = instruction.directive.templateUrl;
+ }
+ } else {
+ componentTemplateUrl = $componentLoader.template(instruction.component);
+ }
return $templateRequest(componentTemplateUrl).then(function (templateHtml) {
return instruction.template = templateHtml;
});
@@ -429,6 +524,7 @@ function pipelineProvider() {
'$setupRoutersStep',
'$initLocalsStep',
'$initControllersStep',
+ '$bindRouteParamsForDirectiveStep',
'$runCanDeactivateHookStep',
'$runCanActivateHookStep',
'$loadTemplatesStep',
@@ -484,6 +580,7 @@ function pipelineProvider() {
function $componentLoaderProvider() {
var DEFAULT_SUFFIX = 'Controller';
+ var DEFAULT_DIRECTIVE_SUFFIX = 'Directive';
var componentToCtrl = function componentToCtrlDefault(name) {
return name[0].toUpperCase() + name.substr(1) + DEFAULT_SUFFIX;
@@ -498,12 +595,22 @@ function $componentLoaderProvider() {
return name[0].toLowerCase() + name.substr(1, name.length - DEFAULT_SUFFIX.length - 1);
};
+ var componentToDirective = function componentToDirectiveDefault(name) {
+ return name + DEFAULT_DIRECTIVE_SUFFIX;
+ };
+
+ var directiveToComponent = function directiveToComponent(name) {
+ return name.substr(0, name.length - DEFAULT_DIRECTIVE_SUFFIX.length);
+ };
+
return {
$get: function () {
return {
controllerName: componentToCtrl,
template: componentToTemplate,
- component: ctrlToComponent
+ component: ctrlToComponent,
+ directiveName: componentToDirective,
+ directiveToComponent: directiveToComponent
};
},
@@ -532,6 +639,11 @@ function $componentLoaderProvider() {
setTemplateMapping: function(newFn) {
componentToTemplate = newFn;
return this;
+ },
+
+ setDirectiveMapping: function(newFn) {
+ componentToDirective = newFn;
+ return this;
}
};
}
diff --git a/test/compile-introspector.es5.spec.js b/test/compile-introspector.es5.spec.js
new file mode 100644
index 0000000..1c67b70
--- /dev/null
+++ b/test/compile-introspector.es5.spec.js
@@ -0,0 +1,40 @@
+describe('$compileIntrospector', function () {
+
+ var $compileProvider;
+
+ beforeEach(function() {
+ module('ng');
+ module('ngNewRouter');
+ module(function(_$compileProvider_) {
+ $compileProvider = _$compileProvider_;
+ });
+ });
+
+ it('should call the introspector function whenever a controller is registered', inject(function ($compileIntrospector) {
+ var spy = jasmine.createSpy();
+ $compileIntrospector(spy);
+ var DirectiveFactory = function() {
+ return {
+ template: "awesome",
+ controller: function() {}
+ };
+ }
+ DirectiveFactory.$routeConfig = [{ path: '/', component: 'example' }];
+ $compileProvider.directive('some', DirectiveFactory);
+ expect(spy).toHaveBeenCalledWith('some', [{ path: '/', component: 'example' }]);
+ }));
+
+ it('should call the introspector function whenever a controller is registered with array annotations', inject(function ($compileIntrospector) {
+ var spy = jasmine.createSpy();
+ $compileIntrospector(spy);
+ var DirectiveFactory = function(foo) {
+ return {
+ template: "awesome",
+ controller: function() {}
+ };
+ }
+ DirectiveFactory.$routeConfig = [{ path: '/', component: 'example' }];
+ $compileProvider.directive('some', ['foo', DirectiveFactory]);
+ expect(spy).toHaveBeenCalledWith('some', [{ path: '/', component: 'example' }]);
+ }));
+});
diff --git a/test/component-loader.es5.spec.js b/test/component-loader.es5.spec.js
index 42ed6ab..c82cda0 100644
--- a/test/component-loader.es5.spec.js
+++ b/test/component-loader.es5.spec.js
@@ -13,4 +13,12 @@ describe('$componentLoader', function () {
it('should convert a component name to a template URL', inject(function ($componentLoader) {
expect($componentLoader.template('foo')).toBe('./components/foo/foo.html');
}));
+
+ it('should convert a component name to a potential directive', inject(function ($componentLoader) {
+ expect($componentLoader.directiveName('foo')).toBe('fooDirective');
+ }));
+
+ it('should convert a directive name to a component name', inject(function ($componentLoader) {
+ expect($componentLoader.directiveToComponent('fooDirective')).toBe('foo');
+ }));
});
diff --git a/test/router-viewport.es5.spec.js b/test/router-viewport.es5.spec.js
index 20044c5..42ae74d 100644
--- a/test/router-viewport.es5.spec.js
+++ b/test/router-viewport.es5.spec.js
@@ -7,7 +7,8 @@ describe('ngViewport', function () {
$rootScope,
$router,
$templateCache,
- $controllerProvider;
+ $controllerProvider,
+ $compileProvider;
beforeEach(function() {
@@ -17,6 +18,9 @@ describe('ngViewport', function () {
$controllerProvider = _$controllerProvider_;
});
+ module(function(_$compileProvider_) {
+ $compileProvider = _$compileProvider_;
+ });
inject(function(_$compile_, _$rootScope_, _$router_, _$templateCache_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
@@ -45,7 +49,6 @@ describe('ngViewport', function () {
expect(elt.text()).toBe('one');
});
-
// See https://github.com/angular/router/issues/105
it('should warn when instantiating a component with no controller', function () {
put('noController', '{{ 2 + 2 }}
');
@@ -647,6 +650,60 @@ describe('ngViewport', function () {
expect($router.navigating).toBe(false);
});
+ describe("with directives", function() {
+ beforeEach(function() {
+ registerDirectiveComponent('three', '{{three.number}}
', boringController('number', 'three'));
+ registerDirectiveComponent('otherUser', 'hello {{otherUser.myName}}
', function() {}, {
+ bindToController: true,
+ scope: { myName: "=name" }
+ });
+ registerDirectiveComponent('customControllerAs', '{{cheese.number}}', boringController("number", "three"),
+ {
+ controllerAs: 'cheese'
+ })
+ });
+
+ it('should work in a simple case using a directive', function () {
+ compile('');
+
+ $router.config([
+ { path: '/', component: 'three' }
+ ]);
+
+ $router.navigate('/');
+ $rootScope.$digest();
+
+ expect(elt.text()).toBe('three');
+ });
+
+ it('should bind parameters if bindToController is set', function () {
+ $router.config([
+ { path: '/user/:name', component: 'otherUser' }
+ ]);
+ compile('');
+
+ $router.navigate('/user/brian');
+ $rootScope.$digest();
+ expect(elt.text()).toBe('hello brian');
+
+ $router.navigate('/user/igor');
+ $rootScope.$digest();
+ expect(elt.text()).toBe('hello igor');
+ });
+
+ it("should respect the directives controllerAs attribute", function() {
+ compile('');
+
+ $router.config([
+ { path: '/', component: 'customControllerAs' }
+ ]);
+
+ $router.navigate('/');
+ $rootScope.$digest();
+
+ expect(elt.text()).toBe('three');
+ });
+ });
function registerComponent(name, template, config) {
if (!template) {
@@ -668,6 +725,32 @@ describe('ngViewport', function () {
put(name, template);
}
+ function registerDirectiveComponent(name, template, config, ddoOptions) {
+ var ddo = {};
+ if (!template) {
+ template = '';
+ }
+ ddo.template = template;
+ var ctrl;
+ if (!config) {
+ ctrl = function () {};
+ } else if (angular.isArray(config)) {
+ ctrl = function () {};
+ ctrl.$routeConfig = config;
+ } else if (typeof config === 'function') {
+ ctrl = config;
+ } else {
+ ctrl = function () {};
+ ctrl.prototype = config;
+ }
+ ddo.controller = ctrl;
+ // using ng 1.3 for now
+ ddo = angular.extend(ddo, ddoOptions)
+ $compileProvider.directive(name, function() {
+ return ddo;
+ });
+ }
+
function boringController (model, value) {
return function () {
this[model] = value;
@@ -764,4 +847,5 @@ describe('ngViewport animations', function () {
$rootScope.$digest();
return elt;
}
+
});
diff --git a/test/util.es5.js b/test/util.es5.js
index c12ed8d..458b8c7 100644
--- a/test/util.es5.js
+++ b/test/util.es5.js
@@ -29,13 +29,17 @@ function provideHelpers(fn, preInject) {
$rootScope,
$router,
$templateCache,
- $controllerProvider;
+ $controllerProvider,
+ $compileProvider;
module('ng');
module('ngNewRouter');
module(function(_$controllerProvider_) {
$controllerProvider = _$controllerProvider_;
});
+ module(function(_$compileProvider_) {
+ $compileProvider = _$compileProvider_;
+ });
inject(function(_$compile_, _$rootScope_, _$router_, _$templateCache_) {
$compile = _$compile_;
@@ -64,6 +68,32 @@ function provideHelpers(fn, preInject) {
put(name, template);
}
+ function registerDirectiveComponent(name, template, config, ddoOptions) {
+ var ddo = {};
+ if (!template) {
+ template = '';
+ }
+ ddo.template = template;
+ var ctrl;
+ if (!config) {
+ ctrl = function () {};
+ } else if (angular.isArray(config)) {
+ ctrl = function () {};
+ ctrl.$routeConfig = config;
+ } else if (typeof config === 'function') {
+ ctrl = config;
+ } else {
+ ctrl = function () {};
+ ctrl.prototype = config;
+ }
+ ddo.controller = ctrl;
+ // using ng 1.3 for now
+ ddo = angular.extend(ddo, ddoOptions)
+ $compileProvider.directive(name, function() {
+ return ddo;
+ });
+ }
+
function put (name, template) {
$templateCache.put(componentTemplatePath(name), [200, template, {}]);
@@ -77,6 +107,7 @@ function provideHelpers(fn, preInject) {
fn({
registerComponent: registerComponent,
+ registerDirectiveComponent: registerDirectiveComponent,
$router: $router,
put: put,
compile: compile