1
0
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:
Joe Fiorini 2015-05-12 09:46:05 -04:00
parent 66d097c7fa
commit 9d1d8374e1
7 changed files with 300 additions and 0 deletions

View File

@ -33,6 +33,8 @@ import {PortalController} from 'tower/controllers/Portal';
import dataServices from 'tower/services/_data-services';
import dashboardGraphs from 'tower/directives/_dashboard-graphs';
import routeExtensions from 'tower/shared/route-extensions/main';
import {JobDetailController} from 'tower/controllers/JobDetail';
import {JobStdoutController} from 'tower/controllers/JobStdout';
import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from 'tower/controllers/JobTemplates';
@ -71,6 +73,7 @@ var tower = angular.module('Tower', [
'RestServices',
dataServices.name,
dashboardGraphs.name,
routeExtensions.name,
'AuthService',
'Utilities',
'LicenseHelper',
@ -184,6 +187,7 @@ var tower = angular.module('Tower', [
$routeProvider.
when('/jobs', {
name: 'jobs',
templateUrl: urlPrefix + 'partials/jobs.html',
controller: JobsListController,
resolve: {
@ -204,6 +208,7 @@ var tower = angular.module('Tower', [
}).
when('/jobs/:id', {
name: 'jobDetail',
templateUrl: urlPrefix + 'partials/job_detail.html',
controller: JobDetailController,
resolve: {

View File

@ -1 +1,2 @@
import 'tower/shared/multi-select-list/main.js';
import 'tower/shared/route-extensions/main.js';

View File

@ -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);
});
}
};
}
]

View 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;
}

View 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);

View File

@ -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;
}
});
}
]

View File

@ -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;
}
}
]