mirror of
https://github.com/ansible/awx.git
synced 2024-11-01 16:51:11 +03:00
Improve how we pass data when changing URLs
This commit adds a couple helpful features to make it easier for us to reference routes, update URLs (both at development time and in the browser at runtime) and pass data between routes. See the UI docs for the specifics on how these features work.
This commit is contained in:
parent
66d097c7fa
commit
9d1d8374e1
@ -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, attrs) {
|
||||||
|
|
||||||
|
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.filter';
|
||||||
|
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,18 @@
|
|||||||
|
export default
|
||||||
|
[ '$rootScope',
|
||||||
|
'$routeParams',
|
||||||
|
function($rootScope, $routeParams) {
|
||||||
|
var offRouteChangeStart =
|
||||||
|
$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, prevRoute, 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