/**
* @ngdoc interface
* @name stateDefinitions
* @description An API for generating a standard set of state definitions
* generateTree - builds a full list/form tree
* generateListNode - builds a single list node e.g. {name: 'projects', ...}
* generateFormNode - builds a form node definition e.g. {name: 'projects.add', ...}
* generateFormListDefinitions - builds form list definitions attached to a form node e.g. {name: 'projects.edit.permissions', ...}
* generateLookupNodes - Attaches to a form node. Builds an abstract '*.lookup' node with field-specific 'lookup.*' children e.g. {name: 'projects.add.lookup.organizations', ...}
*/
export default ['$injector', '$stateExtender', '$log', function($injector, $stateExtender, $log) {
return {
/**
* @ngdoc method
* @name stateDefinitions.generateTree
* @description intended for consumption by $stateProvider.state.lazyLoad in a placeholder node
* @param {object} params
{
parent: 'stateName', // the name of the top-most node of this tree
modes: ['add', 'edit'], // form modes to include in this state tree
list: 'InjectableListDefinition',
form: 'InjectableFormDefinition',
controllers: {
list: 'Injectable' || Object,
add: 'Injectable' || Object,
edit: 'Injectable' || Object,
}
* @returns {object} Promise which resolves to an object.state containing array of all state definitions in this tree
* e.g. {state: [{...}, {...}, ...]}
*/
generateTree: function(params) {
let form, list, formStates, listState,
states = [];
//return defer.promise;
return new Promise((resolve) => {
// returns array of the following states:
// resource.add, resource.edit
// resource.add.lookup, resource.add.lookup.* => [field in form.fields if field.type == 'lookup']
// resource.edit.lookup, resource.edit.lookup.* => [field in form.fields if field.type == 'lookup']
// resource.edit.* => [relationship in form.related]
if (params.list) {
list = $injector.get(params.list);
listState = this.generateListNode(list, params);
states.push(listState);
}
if (params.form) {
// handle inconsistent typing of form definitions
// can be either an object or fn
form = $injector.get(params.form);
form = typeof(form) === 'function' ? form() : form;
formStates = _.map(params.modes, (mode) => this.generateFormNode(mode, form, params));
states = states.concat(_.flatten(formStates));
}
$log.debug('*** Generated State Tree', states);
resolve({ states: states });
});
},
/**
* @ngdoc method
* @name stateDefinitions.generateListNode
* @description builds single list node
* @params {object} list - list definition/configuration object
* @params {object} params
* @returns {object} a list state definition
*/
generateListNode: function(list, params) {
let state,
url = params.urls && params.urls.list ? params.urls.list : (params.url ? params.url : `/${list.name}`);
// allows passed-in params to specify a custom templateUrl
// otherwise, use html returned by generateList.build() to fulfill templateProvider fn
function generateTemplateBlock() {
if (params.templates && params.templates.list) {
return params.templates.list;
} else {
return function(ListDefinition, generateList) {
let html = generateList.build({
list: ListDefinition,
mode: 'edit'
});
html = generateList.wrapPanel(html);
// generateList.formView() inserts a ui-view="form" inside the list view's hierarchy
return generateList.insertFormView() + html;
};
}
}
state = $stateExtender.buildDefinition({
searchPrefix: list.iterator,
name: params.parent,
url: url,
data: params.data,
ncyBreadcrumb: {
label: list.title
},
resolve: {
Dataset: [params.list, 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
],
ListDefinition: () => list
},
views: {
'@': {
// resolves to a variable property name:
// 'templateUrl' OR 'templateProvider'
[params.templates && params.templates.list ? 'templateUrl' : 'templateProvider']: generateTemplateBlock(),
controller: params.controllers.list,
}
}
});
// allow passed-in params to override default resolve block
if (params.resolve && params.resolve.list) {
state.resolve = _.merge(state.resolve, params.resolve.list);
}
// allow passed-in params to override default ncyBreadcrumb property
if (params.ncyBreadcrumb) {
state.ncyBreadcrumb = params.ncyBreadcrumb;
}
if (list.search) {
state.params[`${list.iterator}_search`].value = _.merge(state.params[`${list.iterator}_search`].value, list.search);
}
return state;
},
/**
* @ngdoc method
* @name stateDefinitions.generateFormNode
* @description builds a node of form states, e.g. resource.edit.** or resource.add.**
* @param {string} mode - 'add' || 'edit' - the form mode of this node
* @param {object} form - form definition/configuration object
* @returns {array} Array of state definitions required by form mode [{...}, {...}, ...]
*/
generateFormNode: function(mode, form, params) {
let formNode,
states = [],
url;
switch (mode) {
case 'add':
url = params.urls && params.urls.add ? params.urls.add : (params.url ? params.url : '/add');
// breadcrumbName necessary for resources that are more than one word like
// job templates. form.name can't have spaces in it or it busts form gen
formNode = $stateExtender.buildDefinition({
name: params.name || `${params.parent}.add`,
url: url,
ncyBreadcrumb: {
[params.parent ? 'parent' : null]: `${params.parent}`,
label: `CREATE ${form.breadcrumbName || form.name}`
},
views: {
'form': {
templateProvider: function(FormDefinition, GenerateForm) {
let form = typeof(FormDefinition) === 'function' ?
FormDefinition() : FormDefinition;
return GenerateForm.buildHTML(form, {
mode: 'add',
related: false
});
},
controller: params.controllers.add
}
},
resolve: {
'FormDefinition': [params.form, function(definition) {
return definition;
}]
}
});
if (params.resolve && params.resolve.add) {
formNode.resolve = _.merge(formNode.resolve, params.resolve.add);
}
break;
case 'edit':
url = params.urls && params.urls.edit ? params.urls.edit : (params.url ? params.url : `/:${form.name}_id`);
formNode = $stateExtender.buildDefinition({
name: params.name || `${params.parent}.edit`,
url: url,
ncyBreadcrumb: {
[params.parent ? 'parent' : null]: `${params.parent}`,
label: '{{parentObject.name || name}}'
},
views: {
'form': {
templateProvider: function(FormDefinition, GenerateForm) {
let form = typeof(FormDefinition) === 'function' ?
FormDefinition() : FormDefinition;
return GenerateForm.buildHTML(form, {
mode: 'edit'
});
},
controller: params.controllers.edit
}
},
resolve: {
FormDefinition: [params.form, function(definition) {
return definition;
}],
resourceData: ['FormDefinition', 'Rest', '$stateParams', 'GetBasePath',
function(FormDefinition, Rest, $stateParams, GetBasePath) {
let form, path;
form = typeof(FormDefinition) === 'function' ?
FormDefinition() : FormDefinition;
if (GetBasePath(form.basePath) === undefined && GetBasePath(form.stateTree) === undefined ){
throw { name: 'NotImplementedError', message: `${form.name} form definition is missing basePath or stateTree property.` };
}
else{
path = (GetBasePath(form.basePath) || GetBasePath(form.stateTree) || form.basePath) + $stateParams[`${form.name}_id`];
}
Rest.setUrl(path);
return Rest.get();
}
]
}
});
if (params.resolve && params.resolve.edit) {
formNode.resolve = _.merge(formNode.resolve, params.resolve.edit);
}
break;
}
states.push(formNode);
states = states.concat(this.generateLookupNodes(form, formNode)).concat(this.generateFormListDefinitions(form, formNode));
return states;
},
/**
* @ngdoc method
* @name stateDefinitions.generateFormListDefinitions
* @description builds state definitions for a form's related lists, like notifications/permissions
* @param {object} form - form definition/configuration object
* @params {object} formStateDefinition - the parent form node
* @returns {array} Array of state definitions [{...}, {...}, ...]
*/
generateFormListDefinitions: function(form, formStateDefinition) {
function buildRbacUserTeamDirective(){
let states = [];
states.push($stateExtender.buildDefinition({
name: `${formStateDefinition.name}.permissions.add`,
squashSearchUrl: true,
url: '/add-permissions',
params: {
project_search: {
value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
dynamic: true
},
job_template_search: {
value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
dynamic: true
},
workflow_template_search: {
value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
dynamic: true
},
inventory_search: {
value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
dynamic: true
},
credential_search: {
value: {order_by: 'name', page_size: '5', role_level: 'admin_role'},
dynamic: true
}
},
views: {
[`modal@${formStateDefinition.name}`]: {
template: ``
}
},
resolve: {
jobTemplatesDataset: ['QuerySet', '$stateParams', 'GetBasePath',
function(qs, $stateParams, GetBasePath) {
let path = GetBasePath('job_templates');
return qs.search(path, $stateParams.job_template_search);
}
],
workflowTemplatesDataset: ['QuerySet', '$stateParams', 'GetBasePath',
function(qs, $stateParams, GetBasePath) {
let path = GetBasePath('workflow_job_templates');
return qs.search(path, $stateParams.workflow_template_search);
}
],
projectsDataset: ['ProjectList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
],
inventoriesDataset: ['InventoryList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
],
credentialsDataset: ['CredentialList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
],
},
onExit: function($state) {
if ($state.transition) {
$('#add-permissions-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
},
}));
return states;
}
function buildRbacResourceDirective() {
let states = [];
states.push($stateExtender.buildDefinition({
name: `${formStateDefinition.name}.permissions.add`,
squashSearchUrl: true,
url: '/add-permissions',
params: {
user_search: {
value: { order_by: 'username', page_size: '5' },
dynamic: true,
},
team_search: {
value: { order_by: 'name', page_size: '5' },
dynamic: true
}
},
views: {
[`modal@${formStateDefinition.name}`]: {
template: ``
}
},
resolve: {
usersDataset: ['addPermissionsUsersList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams.user_search);
}
],
teamsDataset: ['addPermissionsTeamsList', 'QuerySet', '$stateParams', 'GetBasePath',
function(list, qs, $stateParams, GetBasePath) {
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
return qs.search(path, $stateParams.team_search);
}
]
},
onExit: function($state) {
if ($state.transition) {
$('#add-permissions-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
},
}));
return states;
}
function buildListNodes(field) {
let states = [];
states.push(buildListDefinition(field));
if (field.iterator === 'permission' && field.actions && field.actions.add) {
if (form.name === 'user' || form.name === 'team'){
states.push(buildRbacUserTeamDirective());
}
else {
states.push(buildRbacResourceDirective());
}
states = _.flatten(states);
}
return states;
}
function buildListDefinition(field) {
let state,
list = field.include ? $injector.get(field.include) : field;
state = $stateExtender.buildDefinition({
searchPrefix: `${list.iterator}`,
name: `${formStateDefinition.name}.${list.iterator}s`,
url: `/${list.iterator}s`,
ncyBreadcrumb: {
parent: `${formStateDefinition.name}`,
label: `${field.iterator}s`
},
params: {
[list.iterator + '_search']: {
value: { order_by: field.order_by ? field.order_by : 'name' }
},
},
views: {
'related': {
templateProvider: function(FormDefinition, GenerateForm) {
let html = GenerateForm.buildCollection({
mode: 'edit',
related: `${list.iterator}s`,
form: typeof(FormDefinition) === 'function' ?
FormDefinition() : FormDefinition
});
return html;
},
controller: ['$scope', 'ListDefinition', 'Dataset',
function($scope, list, Dataset) {
$scope.list = list;
$scope[`${list.iterator}_dataset`] = Dataset.data;
$scope[`${list.iterator}s`] = $scope[`${list.iterator}_dataset`].results;
}
]
}
},
resolve: {
ListDefinition: () => {
return list;
},
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope',
(list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => {
// allow related list definitions to use interpolated $rootScope / $stateParams in basePath field
let path, interpolator;
if (GetBasePath(list.basePath)) {
path = GetBasePath(list.basePath);
} else {
interpolator = $interpolate(list.basePath);
path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams });
}
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
]
}
});
// appy any default search parameters in form definition
if (field.search) {
state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search);
}
return state;
}
return _(form.related).map(buildListNodes).flatten().value();
},
/**
* @ngdoc method
* @name stateDefinitions.generateLookupNode
* @description builds a node of child states for each lookup field in a form
* @param {object} form - form definition/configuration object
* @params {object} formStateDefinition - the parent form node
* @returns {array} Array of state definitions [{...}, {...}, ...]
*/
generateLookupNodes: function(form, formStateDefinition) {
function buildFieldDefinition(field) {
let state = $stateExtender.buildDefinition({
searchPrefix: field.sourceModel,
//squashSearchUrl: true, @issue enable
name: `${formStateDefinition.name}.${field.sourceModel}`,
url: `/${field.sourceModel}?selected`,
// a lookup field's basePath takes precedence over generic list definition's basePath, if supplied
data: {
basePath: field.basePath || null,
formChildState: true
},
params: {
[field.sourceModel + '_search']: {
value: { page_size: '5' }
}
},
ncyBreadcrumb: {
skip: true
},
views: {
'modal': {
templateProvider: function(ListDefinition, generateList) {
let list_html = generateList.build({
mode: 'lookup',
list: ListDefinition,
input_type: 'radio'
});
return `${list_html}`;
}
}
},
resolve: {
ListDefinition: [field.list, function(list) {
list.iterator = field.sourceModel;
return list;
}],
Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', '$state',
(list, qs, $stateParams, GetBasePath, $interpolate, $rootScope, $state) => {
// allow lookup field definitions to use interpolated $stateParams / $rootScope in basePath field
// the basePath on a form's lookup field will take precedence over the general model list's basepath
let path, interpolator;
if ($state.transition._targetState._definition.data && GetBasePath($state.transition._targetState._definition.data.basePath)) {
path = GetBasePath($state.transition._targetState._definition.data.basePath);
} else if ($state.transition._targetState._definition.data && $state.transition._targetState._definition.data.basePath) {
interpolator = $interpolate($state.transition._targetState._definition.data.basePath);
path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams });
} else if (GetBasePath(list.basePath)) {
path = GetBasePath(list.basePath);
} else {
interpolator = $interpolate(list.basePath);
path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams });
}
return qs.search(path, $stateParams[`${list.iterator}_search`]);
}
]
},
onExit: function($state) {
if ($state.transition) {
$('#form-modal').modal('hide');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
},
});
if (field.search) {
state.params[`${field.sourceModel}_search`].value = _.merge(state.params[`${field.sourceModel}_search`].value, field.search);
}
return state;
}
return _(form.fields).filter({ type: 'lookup' }).map(buildFieldDefinition).value();
}
};
}];