diff --git a/Gruntfile.js b/Gruntfile.js
index 16d430b..503ec95 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,5 +1,4 @@
module.exports = function(grunt) {
-
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
@@ -50,6 +49,14 @@ module.exports = function(grunt) {
autoWatch: false,
singleRun: true
}
+ },
+ angular15: {
+ options: {
+ keepalive: true,
+ configFile: 'karma-angular-1.5.conf.js',
+ autoWatch: false,
+ singleRun: true
+ }
}
},
@@ -59,15 +66,35 @@ module.exports = function(grunt) {
prop: 'gitdescribe'
}
}
+ },
+
+ "dgeni-alive": {
+ options: {
+ serve: {
+ port: '10000',
+ openBrowser: false
+ },
+ },
+ api: {
+ title: '<%= pkg.title %>',
+ version: '<%= pkg.version %>',
+ expand: false,
+ src: [
+ 'src/**/*.js',
+ 'docs/**/*.ngdoc'
+ ],
+ dest: 'build/docs'
+ }
}
-
+
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-git-describe');
-
+ grunt.loadNpmTasks('dgeni-alive');
+
grunt.registerTask('default', ['concat:prod']);
grunt.registerTask('prod', ['git-describe:run', 'concat:prod', 'uglify']);
};
diff --git a/bower.json b/bower.json
index cae0bca..bdbf59c 100644
--- a/bower.json
+++ b/bower.json
@@ -13,6 +13,7 @@
"karma-angular-1.2.conf.js",
"karma-angular-1.3.conf.js",
"karma-angular-1.4.conf.js",
+ "karma-angular-1.5.conf.js",
"package.json",
"src",
"**/*.min.js"
diff --git a/docs/api/index.ngdoc b/docs/api/index.ngdoc
new file mode 100644
index 0000000..d5f7751
--- /dev/null
+++ b/docs/api/index.ngdoc
@@ -0,0 +1,16 @@
+@ngdoc overview
+@name index
+@area api
+@title API Docs
+@description
+# Angular-route-segment API Docs
+
+Welcome to the angular-route-segment API docs page. These pages contain the reference materials for version .
+
+## Modules
+
+## {@link route-segment route-segment (routing module)}
+Module defines routing configuration.
+
+## {@link view-segment view-segment (presentation module)}
+Module defines routing views and helper components.
diff --git a/karma-angular-1.2.conf.js b/karma-angular-1.2.conf.js
index 26fd454..8d59795 100644
--- a/karma-angular-1.2.conf.js
+++ b/karma-angular-1.2.conf.js
@@ -3,11 +3,12 @@ module.exports = function(config) {
basePath: './',
- frameworks: ["jasmine"],
+ frameworks: [
+ 'jasmine',
+ 'jasmine-jquery-matchers'
+ ],
files: [
- 'test/lib/jquery.min.js',
- 'test/lib/helpers.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular-route.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular-animate.js',
diff --git a/karma-angular-1.3.conf.js b/karma-angular-1.3.conf.js
index c99f2a7..707b6c5 100644
--- a/karma-angular-1.3.conf.js
+++ b/karma-angular-1.3.conf.js
@@ -3,11 +3,12 @@ module.exports = function(config) {
basePath: './',
- frameworks: ["jasmine"],
+ frameworks: [
+ 'jasmine',
+ 'jasmine-jquery-matchers'
+ ],
files: [
- 'test/lib/jquery.min.js',
- 'test/lib/helpers.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular-route.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular-animate.js',
diff --git a/karma-angular-1.4.conf.js b/karma-angular-1.4.conf.js
index cab9e2f..13ea414 100644
--- a/karma-angular-1.4.conf.js
+++ b/karma-angular-1.4.conf.js
@@ -3,11 +3,12 @@ module.exports = function(config) {
basePath: './',
- frameworks: ["jasmine"],
+ frameworks: [
+ 'jasmine',
+ 'jasmine-jquery-matchers'
+ ],
files: [
- 'test/lib/jquery.min.js',
- 'test/lib/helpers.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular-route.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular-animate.js',
diff --git a/karma-angular-1.5.conf.js b/karma-angular-1.5.conf.js
new file mode 100644
index 0000000..799dcdb
--- /dev/null
+++ b/karma-angular-1.5.conf.js
@@ -0,0 +1,26 @@
+module.exports = function(config) {
+ config.set({
+
+ basePath: './',
+
+ frameworks: [
+ 'jasmine',
+ 'jasmine-jquery-matchers'
+ ],
+
+ files: [
+ 'https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.1/angular.js',
+ 'https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.1/angular-route.js',
+ 'https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.1/angular-animate.js',
+ 'https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.1/angular-mocks.js',
+ 'https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.1/angular-scenario.js',
+ 'src/**/*.js',
+ 'test/unit/**/*.js'
+ ],
+
+ autoWatch: true,
+
+ browsers: ['PhantomJS']
+
+ });
+};
\ No newline at end of file
diff --git a/package.json b/package.json
index fc67dfe..e6435d0 100644
--- a/package.json
+++ b/package.json
@@ -1,19 +1,26 @@
{
- "name": "angular-route-segment",
- "version": "1.5.0",
- "devDependencies": {
- "grunt": "",
- "grunt-contrib-uglify": "",
- "grunt-contrib-concat": "",
- "grunt-karma": "",
- "karma": "",
- "karma-jasmine": "0.1.5",
- "karma-phantomjs-launcher": "",
- "karma-junit-reporter": "",
- "grunt-git-describe": ""
- },
- "main": "build/angular-route-segment.js",
- "scripts": {
- "test": "grunt karma"
- }
+ "name": "angular-route-segment",
+ "title": "Angular Route Segment",
+ "version": "1.5.0",
+ "devDependencies": {
+ "dgeni-alive": "~0.2.0",
+ "grunt": "",
+ "grunt-contrib-concat": "",
+ "grunt-contrib-uglify": "",
+ "grunt-git-describe": "",
+ "grunt-karma": "",
+ "jasmine-core": "~2.4.1",
+ "jasmine-jquery-matchers": "~1.0.1",
+ "jquery": "~2.2.0",
+ "karma": "~0.13.19",
+ "karma-jasmine": "~0.3.6",
+ "karma-jasmine-jquery-matchers": "~1.0.0",
+ "karma-junit-reporter": "",
+ "karma-phantomjs-launcher": "~1.0.0",
+ "phantomjs-prebuilt": "~2.1.3"
+ },
+ "main": "build/angular-route-segment.js",
+ "scripts": {
+ "test": "grunt karma"
+ }
}
diff --git a/src/route-segment.js b/src/route-segment.js
index fd5c56e..4794618 100644
--- a/src/route-segment.js
+++ b/src/route-segment.js
@@ -1,71 +1,213 @@
+/**
+ * angular-route-segment v1.5.0
+ * https://angular-route-segment.com
+ * @author Artem Chivchalov
+ * @license MIT License http://opensource.org/licenses/MIT
+ */
'use strict';
-
(function(angular) {
+/**
+ * @ngdoc module
+ * @module route-segment
+ * @name route-segment
+ * @packageName angular-route-segment
+ * @requires ngRoute
+ * @description
+ * This library is intended to provide the lacking functionality of nested routing to [AngularJS](http://angularjs.org) applications.
+ * It is widely known, there are no ways to keep the parent state unchanged when children are updated via routing mechanics - the
+ * [$route](https://docs.angularjs.org/api/ngRoute/service/$route) service re-creates the whole scope after a route is changed, losing its state completely.
+ * *route-segment* gives you a way to handle this.
+ *
+ * The library provides two pieces of code: {@link $routeSegment $routeSegment} service and {@link appViewSegment appViewSegment} directive.
+ * Both are placed in their own modules which you must include as dependencies in your app module:
+ *
+ * ```js
+ * var app = angular.module('app', ['ngRoute', 'route-segment', 'view-segment']);
+ * ```
+ * $routeSegment is a layer on top of built-in Angular [$route](https://docs.angularjs.org/api/ngRoute/service/$route) service and is meant to be used instead of it.
+ * Its provider exposes configuration methods which can be used to traverse the tree of route segments and setup it properly.
+ */
var mod = angular.module( 'route-segment', [] );
+/**
+ * @ngdoc provider
+ * @module route-segment
+ * @name $routeSegmentProvider
+ * @requires https://docs.angularjs.org/api/ngRoute/provider/$routeProvider $routeProvider
+ * @description Replaces [$routeProvider](https://docs.angularjs.org/api/ngRoute/provider/$routeProvider) for the app routing configuration
+ *
+ * Example:
+ * ```js
+ * app.config(function ($routeSegmentProvider) {
+ *
+ * $routeSegmentProvider.
+ *
+ * when('/section1', 's1').
+ * when('/section1/prefs', 's1.prefs').
+ * when('/section1/:id', 's1.itemInfo').
+ * when('/section1/:id/edit', 's1.itemInfo.edit').
+ * when('/section2', 's2').
+ *
+ * segment('s1', {
+ * templateUrl: 'templates/section1.html',
+ * controller: MainCtrl}).
+ *
+ * within().
+ *
+ * segment('home', {
+ * default: true,
+ * templateUrl: 'templates/section1/home.html'}).
+ *
+ * segment('itemInfo', {
+ * templateUrl: 'templates/section1/item.html',
+ * controller: Section1ItemCtrl,
+ * dependencies: ['id']}).
+ *
+ * within().
+ *
+ * segment('overview', {
+ * default: true
+ * templateUrl: 'templates/section1/item/overview.html'}).
+ *
+ * segment('edit', {
+ * templateUrl: 'templates/section1/item/edit.html'}).
+ *
+ * up().
+ *
+ * segment('prefs', {
+ * templateUrl: 'templates/section1/prefs.html'}).
+ *
+ * up().
+ *
+ * segment('s2', {
+ * templateUrl: 'templates/section2.html',
+ * controller: MainCtrl});
+ * });
+ * ```
+ *
+ * Alternatively, you can use this syntax instead of traversing (useful if you want modules to have their own separately defined routes):
+ * ```js
+ * $routeSegmentProvider.segment('s1', {
+ * templateUrl: 'templates/section1.html',
+ * controller: MainCtrl});
+ *
+ * $routeSegmentProvider.within('s1').segment('home', {
+ * templateUrl: 'templates/section1/home.html'});
+ *
+ * $routeSegmentProvider.within('s1').segment('itemInfo', {
+ * templateUrl: 'templates/section1/item.html',
+ * controller: Section1ItemCtrl,
+ * dependencies: ['id']});
+ *
+ * $routeSegmentProvider.within('s1').within('itemInfo').segment('overview', {
+ * templateUrl: 'templates/section1/item/overview.html'});
+ * ```
+ *
+ * See {@link appViewSegment appViewSegment} for details on views configuration
+ */
mod.provider( '$routeSegment',
- ['$routeProvider', function($routeProvider) {
-
+ ['$routeProvider', function $routeSegmentProvider ($routeProvider) {
+
var $routeSegmentProvider = this;
-
+
+ /**
+ * @ngdoc type
+ * @module route-segment
+ * @name $routeSegmentProvider.options
+ * @description Contains configuration options for {@link $routeSegmentProvider $routeSegmentProvider}
+ */
+ /**
+ * @ngdoc property
+ * @name $routeSegmentProvider#options
+ * @type {$routeSegmentProvider.options}
+ * @description Provider configuration object
+ */
var options = $routeSegmentProvider.options = {
-
+
/**
- * When true, it will resolve `templateUrl` automatically via $http service and put its
- * contents into `template`.
- * @type {boolean}
+ * @ngdoc property
+ * @name $routeSegmentProvider.options#autoLoadTemplates
+ * @type {Boolean}
+ * @description
+ * When true, it will resolve `templateUrl` automatically via ($http)[https://docs.angularjs.org/api/ng/service/$http]
+ * service and put its contents into `template`.
*/
autoLoadTemplates: true,
-
+
/**
+ * @ngdoc property
+ * @name $routeSegmentProvider.options#strictMode
+ * @type {Boolean}
+ * @description
* When true, all attempts to call `within` method on non-existing segments will throw an error (you would
* usually want this behavior in production). When false, it will transparently create new empty segment
* (can be useful in isolated tests).
- * @type {boolean}
*/
strictMode: false
};
-
+
+ /**
+ * @ngdoc property
+ * @name $routeSegmentProvider#property
+ * @type {Object}
+ * @private
+ * @description Registered routing segments
+ */
var segments = this.segments = {},
rootPointer = pointer(segments, null),
segmentRoutes = {};
-
+
function camelCase(name) {
return name.replace(/([\:\-\_]+(.))/g, function(_, separator, letter, offset) {
return offset ? letter.toUpperCase() : letter;
});
}
-
+
+ /**
+ * @ngdoc type
+ * @module route-segment
+ * @name $routeSegmentProvider.Pointer
+ * @description Segment traversal element.
+ *
+ * Each {@link $routeSegmentProvider#segment $routeSegmentProvider#segment} call creates new navigation pointer
+ */
function pointer(segment, parent) {
-
+
if(!segment)
throw new Error('Invalid pointer segment');
-
+
var lastAddedName;
-
+
return {
-
+
/**
- * Adds new segment at current pointer level.
- *
- * @param string} name Name of a segment.
+ * @ngdoc method
+ * @name $routeSegmentProvider#segment
+ * @see $routeSegmentProvider.Pointer#segment
+ */
+ /**
+ * @ngdoc method
+ * @name $routeSegmentProvider.Pointer#segment
+ * @param {string} name Name of a segment.
* @param {Object} params Segment's parameters hash. The following params are supported:
- * - `template` provides HTML for the given segment view;
- * - `templateUrl` is a template should be fetched from network via this URL;
- * - `controller` is attached to the given segment view when compiled and linked,
- * this can be any controller definition AngularJS supports;
- * - `dependencies` is an array of route param names which are forcing the view
- * to recreate when changed;
- * - `watcher` is a $watch-function for recreating the view when its returning value
- * is changed;
- * - `resolve` is a hash of functions or injectable names which should be resolved
- * prior to instantiating the template and the controller;
- * - `untilResolved` is the alternate set of params (e.g. `template` and `controller`)
- * which should be used before resolving is completed;
- * - `resolveFailed` is the alternate set of params which should be used
- * if resolving failed;
- *
- * @returns {Object} The same level pointer.
+ * @param {String|Function} [params.template] provides HTML for the given segment view;
+ * @param {String|Function} [params.templateUrl] template to fetch from network via this URL;
+ * @param {String|Function} [params.controller] cotroller attached to the given segment view when compiled and linked,
+ * this can be any controller definition AngularJS supports;
+ * @param {String} [params.controllerAs] controller alias name, if present the controller will be
+ * published to scope under the controllerAs name
+ * @param {Array.} [params.dependencies] array of route param names which are forcing the view to recreate when changed;
+ * @param {Function} [params.watcher] $watch-function for recreating the view when its returning value is changed;
+ * @param {Object.} resolve hash of functions or injectable names which should be resolved
+ * prior to instantiating the template and the controller;
+ * @param {Object} [params.untilResolved] alternate set of params (e.g. `template` and `controller`)
+ * which should be used before resolving is completed;
+ * @param {Object} [params.resolveFailed] alternate set of params which should be used if resolving failed;
+ * @param {Boolean} [params.default] when set to true this child segment should be loaded by
+ * default when no child is specified in the route.
+ * @returns {$routeSegmentProvider.Pointer} The same level pointer.
+ * @description Adds new segment at current pointer level.
+ *
*/
segment: function(name, params) {
segment[camelCase(name)] = {name: name, params: params};
@@ -74,16 +216,23 @@ mod.provider( '$routeSegment',
},
/**
- * Traverses into an existing segment, so that subsequent `segment` calls
+ * @ngdoc method
+ * @name $routeSegmentProvider#within
+ * @see $routeSegmentProvider.Pointer#within
+ */
+ /**
+ * @ngdoc method
+ * @name $routeSegmentProvider.Pointer#within
+ * @param {String=} childName An existing segment's name. If undefined, then the last added segment is selected.
+ * @returns {$routeSegmentProvider.Pointer} The pointer to the child segment.
+ * @throws {Error} when {@link $routeSegmentProvider.options#strictMode $routeSegmentProvider.options#strictMode} is true and segment with given name is not found
+ * @description Traverses into an existing segment, so that subsequent `segment` calls
* will add new segments as its descendants.
- *
- * @param {string} childName An existing segment's name. If undefined, then the last added segment is selected.
- * @returns {Object} The pointer to the child segment.
- */
- within: function(childName) {
+ */
+ within: function(childName) {
var child;
childName = childName || lastAddedName;
-
+
if(child = segment[camelCase(childName)]) {
if(child.children == undefined)
child.children = {};
@@ -93,34 +242,41 @@ mod.provider( '$routeSegment',
throw new Error('Cannot get into unknown `'+childName+'` segment');
else {
child = segment[camelCase(childName)] = {params: {}, children: {}};
- }
+ }
}
return pointer(child.children, this);
},
-
+
/**
- * Traverses up in the tree.
- * @returns {Object} The pointer which are parent to the current one;
+ * @ngdoc method
+ * @name $routeSegmentProvider.Pointer#up
+ * @returns {$routeSegmentProvider.Pointer} The pointer which are parent to the current one;
+ * @description Traverses up in the tree.
*/
up: function() {
return parent;
},
-
+
/**
- * Traverses to the root.
- * @returns The root pointer.
+ * @ngdoc method
+ * @name $routeSegmentProvider.Pointer#up
+ * @returns {$routeSegmentProvider.Pointer} The root pointer.
+ * @description Traverses to the root.
*/
root: function() {
return rootPointer;
}
}
}
-
+
/**
- * The shorthand for $routeProvider.when() method with specified route name.
- * @param {string} path Route URL, e.g. '/foo/bar'
- * @param {string} name Fully qualified route name, e.g. 'foo.bar'
+ * @ngdoc method
+ * @name $routeSegmentProvider#when
+ * @param {String} path Route URL, e.g. '/foo/bar'
+ * @param {String} name Fully qualified route name, e.g. 'foo.bar'
* @param {Object} route Mapping information to be assigned to $route.current on route match.
+ * @returns {$routeSegmentProvider} instance
+ * @description The shorthand for [$routeProvider.when()](https://docs.angularjs.org/api/ngRoute/provider/$routeProvider#when) method with specified route name.
*/
$routeSegmentProvider.when = function(path, name, route) {
if (route == undefined)
@@ -131,57 +287,82 @@ mod.provider( '$routeSegment',
segmentRoutes[name] = path;
return this;
};
-
+
// Extending the provider with the methods of rootPointer
// to start configuration.
angular.extend($routeSegmentProvider, rootPointer);
-
-
+
+
// the service factory
- this.$get = ['$rootScope', '$q', '$http', '$templateCache', '$route', '$routeParams', '$injector',
- function($rootScope, $q, $http, $templateCache, $route, $routeParams, $injector) {
-
- var $routeSegment = {
-
+ /**
+ * @ngdoc service
+ * @module route-segment
+ * @name $routeSegment
+ * @requires https://docs.angularjs.org/api/ng/service/$rootScope $rootScope
+ * @requires https://docs.angularjs.org/api/ng/service/$q $q
+ * @requires https://docs.angularjs.org/api/ng/service/$http $http
+ * @requires https://docs.angularjs.org/api/ng/service/$templateCache $templateCache
+ * @requires https://docs.angularjs.org/api/ngRoute/service/$route $route
+ * @requires https://docs.angularjs.org/api/ngRoute/service/$routeParams $routeParams
+ * @requires https://docs.angularjs.org/api/auto/service/$injector $injector
+ * @requires https://docs.angularjs.org/api/ng/service/$location $location
+ * @description Provides state and operations for the current segment
+ */
+ this.$get = ['$rootScope', '$q', '$http', '$templateCache', '$route', '$routeParams', '$injector', '$location',
+ function($rootScope, $q, $http, $templateCache, $route, $routeParams, $injector, $location) {
+
+ var $routeSegment = {
+
/**
- * Fully qualified name of current active route
- * @type {string}
+ * @ngdoc property
+ * @name $routeSegment#name
+ * @type {String}
+ * @description Fully qualified name of current active route
*/
name: '',
/**
- * A copy of `$routeParams` in its state of the latest successful segment update. It may be not equal
- * to `$routeParams` while some resolving is not completed yet. Should be used instead of original
- * `$routeParams` in most cases.
+ * @ngdoc property
+ * @name $routeSegment#$routeParams
* @type {Object}
+ * @description A copy of `$routeParams` in its state of the latest successful segment update.
+ *
+ * It may be not equal to `$routeParams` while some resolving is not completed yet. Should be used instead of original
+ * `$routeParams` in most cases.
*/
$routeParams: angular.copy($routeParams),
-
+
/**
- * Array of segments splitted by each level separately. Each item contains the following properties:
+ * @ngdoc property
+ * @name $routeSegment#chain
+ * @type {Array.<$routeSegment.Segment>}
+ * @description Array of segments splitted by each level separately. Each item contains the following properties:
+ *
* - `name` is the name of a segment;
* - `params` is the config params hash of a segment;
* - `locals` is a hash which contains resolve results if any;
* - `reload` is a function to reload a segment (restart resolving, reinstantiate a controller, etc)
- *
- * @type {Array.