mirror of
https://github.com/ansible/awx.git
synced 2024-11-01 16:51:11 +03:00
Merge pull request #202 from joefiorini/route-extensions
Improve how we pass data when changing URLs
This commit is contained in:
commit
16cc712b9e
@ -33,6 +33,8 @@ import {PortalController} from 'tower/controllers/Portal';
|
|||||||
import dataServices from 'tower/services/_data-services';
|
import dataServices from 'tower/services/_data-services';
|
||||||
import dashboardGraphs from 'tower/directives/_dashboard-graphs';
|
import dashboardGraphs from 'tower/directives/_dashboard-graphs';
|
||||||
|
|
||||||
|
import routeExtensions from 'tower/shared/route-extensions/main';
|
||||||
|
|
||||||
import {JobDetailController} from 'tower/controllers/JobDetail';
|
import {JobDetailController} from 'tower/controllers/JobDetail';
|
||||||
import {JobStdoutController} from 'tower/controllers/JobStdout';
|
import {JobStdoutController} from 'tower/controllers/JobStdout';
|
||||||
import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from 'tower/controllers/JobTemplates';
|
import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from 'tower/controllers/JobTemplates';
|
||||||
@ -71,6 +73,7 @@ var tower = angular.module('Tower', [
|
|||||||
'RestServices',
|
'RestServices',
|
||||||
dataServices.name,
|
dataServices.name,
|
||||||
dashboardGraphs.name,
|
dashboardGraphs.name,
|
||||||
|
routeExtensions.name,
|
||||||
'AuthService',
|
'AuthService',
|
||||||
'Utilities',
|
'Utilities',
|
||||||
'LicenseHelper',
|
'LicenseHelper',
|
||||||
@ -184,6 +187,7 @@ var tower = angular.module('Tower', [
|
|||||||
$routeProvider.
|
$routeProvider.
|
||||||
|
|
||||||
when('/jobs', {
|
when('/jobs', {
|
||||||
|
name: 'jobs',
|
||||||
templateUrl: urlPrefix + 'partials/jobs.html',
|
templateUrl: urlPrefix + 'partials/jobs.html',
|
||||||
controller: JobsListController,
|
controller: JobsListController,
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -204,6 +208,7 @@ var tower = angular.module('Tower', [
|
|||||||
}).
|
}).
|
||||||
|
|
||||||
when('/jobs/:id', {
|
when('/jobs/:id', {
|
||||||
|
name: 'jobDetail',
|
||||||
templateUrl: urlPrefix + 'partials/job_detail.html',
|
templateUrl: urlPrefix + 'partials/job_detail.html',
|
||||||
controller: JobDetailController,
|
controller: JobDetailController,
|
||||||
resolve: {
|
resolve: {
|
||||||
|
@ -1 +1,2 @@
|
|||||||
import 'tower/shared/multi-select-list/main.js';
|
import 'tower/shared/multi-select-list/main.js';
|
||||||
|
import 'tower/shared/route-extensions/main.js';
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
import {lookupRouteUrl} from './lookup-route-url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @ngdoc directive
|
||||||
|
* @name routeExtensions.directive:linkTo
|
||||||
|
* @desription
|
||||||
|
* The `linkTo` directive looks up a route's URL and generates a link to that route. When a user
|
||||||
|
* clicks the link, this directive calls the `transitionTo` factory to send them to the given
|
||||||
|
* URL. For accessibility and fallback purposes, it also sets the `href` attribute of the link
|
||||||
|
* to the path.
|
||||||
|
*
|
||||||
|
* Note that in this example the model object uses a key that matches up with the route parameteer
|
||||||
|
* name in the route url (in this case `:id`).
|
||||||
|
*
|
||||||
|
* **N.B.** The below example currently won't run. It's included to show an example of using
|
||||||
|
* the `linkTo` directive within code. In order for this to run, we will need to run
|
||||||
|
* the code in an iframe (using something like `dgeni` instead of `grunt-ngdocs`).
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
<example module="simpleRouteExample">
|
||||||
|
<file name="script.js">
|
||||||
|
angular.module('simpleRouteExample', ['ngRoute', 'routeExtensions'])
|
||||||
|
.config(['$routeProvider', function($route) {
|
||||||
|
$route.when('/posts/:id', {
|
||||||
|
name: 'post',
|
||||||
|
template: '<h1>{{post.title}}</h1><p>{{post.body}}</p>',
|
||||||
|
controller: 'post'
|
||||||
|
});
|
||||||
|
}]).controller('post', function($scope) {
|
||||||
|
});
|
||||||
|
</file>
|
||||||
|
<file name="index.html">
|
||||||
|
<section ng-init="featuredPost =
|
||||||
|
{ id: 1,
|
||||||
|
title: 'Featured',
|
||||||
|
body: 'This post is featured because it is awesome'
|
||||||
|
};">
|
||||||
|
<link-to route="post" model="{ id: featuredPost }">
|
||||||
|
{{featuredPost.title}}
|
||||||
|
</link-to>
|
||||||
|
</section>
|
||||||
|
</file>
|
||||||
|
</example>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export default
|
||||||
|
[ '$route',
|
||||||
|
'transitionTo',
|
||||||
|
function($routeProvider, transitionTo) {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
transclude: true,
|
||||||
|
template: '<a href="{{url}}" data-transition-to title="{{routeName}}" ng-transclude></a>',
|
||||||
|
scope: {
|
||||||
|
routeName: '@route',
|
||||||
|
model: '&'
|
||||||
|
},
|
||||||
|
link: function(scope, element) {
|
||||||
|
|
||||||
|
var model = scope.$eval(scope.model);
|
||||||
|
scope.url = lookupRouteUrl(scope.routeName, $routeProvider.routes, model);
|
||||||
|
|
||||||
|
element.find('[data-transition-to]').on('click', function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
transitionTo(scope.routeName, model);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
43
awx/ui/static/js/shared/route-extensions/lookup-route-url.js
Normal file
43
awx/ui/static/js/shared/route-extensions/lookup-route-url.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
export function lookupRouteUrl(name, routes, models) {
|
||||||
|
var route = _.find(routes, {name: name});
|
||||||
|
|
||||||
|
if (angular.isUndefined(route)) {
|
||||||
|
throw "Unknown route " + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeUrl = route.originalPath;
|
||||||
|
|
||||||
|
if (!angular.isUndefined(models) && angular.isObject(models)) {
|
||||||
|
var match = routeUrl.match(route.regexp);
|
||||||
|
var keyMatchers = match.slice(1);
|
||||||
|
|
||||||
|
routeUrl =
|
||||||
|
keyMatchers.reduce(function(url, keyMatcher) {
|
||||||
|
var value;
|
||||||
|
var key = keyMatcher.replace(/^:/, '');
|
||||||
|
|
||||||
|
var model = models[key];
|
||||||
|
|
||||||
|
if (angular.isArray(model)) {
|
||||||
|
value = _.compact(_.pluck(model, key));
|
||||||
|
|
||||||
|
if (_.isEmpty(value)) {
|
||||||
|
value = _.pluck(model, 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
value = value.join(',');
|
||||||
|
} else if (angular.isObject(model)) {
|
||||||
|
value = model[key];
|
||||||
|
|
||||||
|
if (_.isEmpty(value)) {
|
||||||
|
value = model.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.replace(keyMatcher, value);
|
||||||
|
}, routeUrl);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return routeUrl;
|
||||||
|
}
|
28
awx/ui/static/js/shared/route-extensions/main.js
Normal file
28
awx/ui/static/js/shared/route-extensions/main.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import linkTo from './link-to.directive';
|
||||||
|
import transitionTo from './transition-to.factory';
|
||||||
|
import modelListener from './model-listener.config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc overview
|
||||||
|
* @name routeExtensions
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* # routeExtensions
|
||||||
|
*
|
||||||
|
* Adds a couple useful features to ngRoute:
|
||||||
|
* - Adds a `name` property to route objects; used to identify the route in transitions & links
|
||||||
|
* - Adds the ability to pass model data when clicking a link that goes to a route
|
||||||
|
* - Adds a directive that generates a route's URL from the route name & given models
|
||||||
|
* - Adds the ability to specify models in route resolvers
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
|
*
|
||||||
|
* If you need to generate a link to a route, then use the {@link routeExtensions.directive:linkTo `linkTo directive`}. If you need to transition to a route in JavaScript code, then use the {@link routeExtensions.factory:transitionTo `transitionTo service`}.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export default
|
||||||
|
angular.module('routeExtensions',
|
||||||
|
['ngRoute'])
|
||||||
|
.factory('transitionTo', transitionTo)
|
||||||
|
.run(modelListener)
|
||||||
|
.directive('linkTo', linkTo);
|
@ -0,0 +1,17 @@
|
|||||||
|
export default
|
||||||
|
[ '$rootScope',
|
||||||
|
'$routeParams',
|
||||||
|
function($rootScope, $routeParams) {
|
||||||
|
$rootScope.$on('$routeChangeSuccess', function(e, newRoute) {
|
||||||
|
if (angular.isUndefined(newRoute.model)) {
|
||||||
|
var keys = Object.keys(newRoute.params);
|
||||||
|
var models = keys.reduce(function(model, key) {
|
||||||
|
model[key] = newRoute.locals[key];
|
||||||
|
return model;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
$routeParams.model = models;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
];
|
@ -0,0 +1,131 @@
|
|||||||
|
import {lookupRouteUrl} from './lookup-route-url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ngdoc service
|
||||||
|
* @name routeExtensions.service:transitionTo
|
||||||
|
* @description
|
||||||
|
* The `transitionTo` service generates a URL given a route name and model parameters, then
|
||||||
|
* updates the browser's URL via `$location.path`. Use this in situations where you cannot
|
||||||
|
* use the `linkTo` directive, for example to redirect the user after saving an object.
|
||||||
|
*
|
||||||
|
* @param {string} routeName The name of the route whose URL you want to redirect to (corresponds
|
||||||
|
* name property of route)
|
||||||
|
* @param {object} model The model you want to use to generate the URL and be passed to the new
|
||||||
|
* route. This object follows a strict key/value naming convention where
|
||||||
|
* the keys match the parameters listed in the route's URL. For example,
|
||||||
|
* a URL of `/posts/:id` would require a model object like: `{ id: post }`,
|
||||||
|
* where `post` is the object you want to pass to the new route.
|
||||||
|
*
|
||||||
|
* **N.B.** The below example currently won't run. It's included to show an example of using
|
||||||
|
* the `transitionTo` function within code. In order for this to run, we will need to run
|
||||||
|
* the code in an iframe (using something like `dgeni` instead of `grunt-ngdocs`).
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
<example module="transitionToExample">
|
||||||
|
<file name="script.js">
|
||||||
|
angular.module('transitionToExample',
|
||||||
|
['ngRoute',
|
||||||
|
'routeExtensions'
|
||||||
|
])
|
||||||
|
.config(function($routeProvider, $locationProvider) {
|
||||||
|
$routeProvider
|
||||||
|
.when('/post/:id',
|
||||||
|
{ name: 'post',
|
||||||
|
template: '<h1>{{post.title}}</h1><p>{{post.body}}</p>'
|
||||||
|
});
|
||||||
|
|
||||||
|
$locationProvider.html5Mode(true);
|
||||||
|
$locationProvider.hashPrefix('!');
|
||||||
|
})
|
||||||
|
.controller('post', ['$scope', function($scope) {
|
||||||
|
|
||||||
|
}])
|
||||||
|
.controller('postForm', ['$scope', 'transitionTo', function($scope, transitionTo) {
|
||||||
|
$scope.post = {
|
||||||
|
id: 1,
|
||||||
|
title: 'A post',
|
||||||
|
body: 'Some text'
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.savePost = function() {
|
||||||
|
transitionTo('post', { id: $scope.post });
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
</file>
|
||||||
|
<file name="index.html">
|
||||||
|
<form ng-controller="postForm">
|
||||||
|
<legend>Edit Post</legend>
|
||||||
|
<label>
|
||||||
|
Title
|
||||||
|
<input type="text" ng-model="post.title">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Body
|
||||||
|
<textarea ng-model="post.body"></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button ng-click="savePost()">Save Post</button>
|
||||||
|
</form>
|
||||||
|
</file>
|
||||||
|
|
||||||
|
</example>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
function safeApply(fn, $rootScope) {
|
||||||
|
var currentPhase = $rootScope.$$phase;
|
||||||
|
|
||||||
|
if (currentPhase === '$apply' || currentPhase === '$digest') {
|
||||||
|
fn();
|
||||||
|
} else {
|
||||||
|
$rootScope.$apply(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default
|
||||||
|
[ '$location',
|
||||||
|
'$rootScope',
|
||||||
|
'$route',
|
||||||
|
'$q',
|
||||||
|
function($location, $rootScope, $route, $q) {
|
||||||
|
return function(routeName, model) {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
var url = lookupRouteUrl(routeName, $route.routes, model);
|
||||||
|
|
||||||
|
var offRouteChangeStart =
|
||||||
|
$rootScope.$on('$routeChangeStart', function(e, newRoute) {
|
||||||
|
if (newRoute.$$route.name === routeName) {
|
||||||
|
deferred.resolve(newRoute, model);
|
||||||
|
newRoute.params.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
offRouteChangeStart();
|
||||||
|
});
|
||||||
|
|
||||||
|
var offRouteChangeSuccess =
|
||||||
|
$rootScope.$on('$routeChangeSuccess', function(e, newRoute) {
|
||||||
|
if (newRoute.$$route.name === routeName) {
|
||||||
|
deferred.resolve(newRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
offRouteChangeSuccess();
|
||||||
|
});
|
||||||
|
|
||||||
|
var offRouteChangeError =
|
||||||
|
$rootScope.$on('$routeChangeError', function(e, newRoute, previousRoute, rejection) {
|
||||||
|
if (newRoute.$$route.name === routeName) {
|
||||||
|
deferred.reject(newRoute, previousRoute, rejection);
|
||||||
|
}
|
||||||
|
|
||||||
|
offRouteChangeError();
|
||||||
|
});
|
||||||
|
|
||||||
|
safeApply(function() {
|
||||||
|
$location.path(url);
|
||||||
|
}, $rootScope);
|
||||||
|
|
||||||
|
return deferred;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
Loading…
Reference in New Issue
Block a user