From 0ca53024f05a89d7a5a3d56541ce1d2286b524fa Mon Sep 17 00:00:00 2001 From: gconsidine Date: Wed, 31 May 2017 17:16:26 -0400 Subject: [PATCH] Add support for dynamic select inputs --- .../credentials/add-credentials.controller.js | 1 + awx/ui/client/lib/components/index.js | 2 + .../lib/components/input/group.directive.js | 83 ++++++++++++++----- .../lib/components/input/select.directive.js | 10 +++ .../lib/components/input/select.partial.html | 5 +- .../input/textarea-secret.directive.js | 44 ++++++++++ .../input/textarea-secret.partial.html | 16 ++++ awx/ui/client/lib/models/Credential.js | 2 +- awx/ui/client/lib/models/CredentialType.js | 4 +- 9 files changed, 143 insertions(+), 24 deletions(-) create mode 100644 awx/ui/client/lib/components/input/textarea-secret.directive.js create mode 100644 awx/ui/client/lib/components/input/textarea-secret.partial.html diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 822182b873..0dc60b46e5 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -11,6 +11,7 @@ function AddCredentialsController (models, $state) { vm.form.credential_type._data = credentialType.get('results'); vm.form.credential_type._placeholder = 'SELECT A TYPE'; + vm.form.credential_type._format = 'grouped-object'; vm.form.credential_type._display = 'name'; vm.form.credential_type._key = 'id'; vm.form.credential_type._exp = 'type as type.name group by type.kind for type in state._data'; diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index 0439ac8d72..ecf1d729c9 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -9,6 +9,7 @@ import inputSelect from './input/select.directive'; import inputSecret from './input/secret.directive'; import inputText from './input/text.directive'; import inputTextarea from './input/textarea.directive'; +import inputTextareaSecret from './input/textarea-secret.directive'; import panel from './panel/panel.directive'; import panelHeading from './panel/heading.directive'; import panelBody from './panel/body.directive'; @@ -31,6 +32,7 @@ angular .directive('atInputSelect', inputSelect) .directive('atInputText', inputText) .directive('atInputTextarea', inputTextarea) + .directive('atInputTextareaSecret', inputTextareaSecret) .directive('atPanel', panel) .directive('atPanelHeading', panelHeading) .directive('atPanelBody', panelBody) diff --git a/awx/ui/client/lib/components/input/group.directive.js b/awx/ui/client/lib/components/input/group.directive.js index d33bf6009f..28c155dac3 100644 --- a/awx/ui/client/lib/components/input/group.directive.js +++ b/awx/ui/client/lib/components/input/group.directive.js @@ -56,33 +56,62 @@ function AtInputGroupController ($scope, $compile) { let group = []; inputs.forEach((input, i) => { - if (input.type === 'string') { - if (input.secret && input.multiline) { - input._component = 'at-input-textarea'; - } else if (input.secret) { - input._component = 'at-input-secret'; - } else if (input.multiline) { - input._component = 'at-input-textarea'; - } else { - input._component = 'at-input-text'; - } - } + input = Object.assign(input, vm.getComponentType(input)); group.push(Object.assign({ _element: vm.createElement(input, i), _key: 'inputs', - _group: true + _group: true, + _groupIndex: i }, input)); }); return group; }; + vm.getComponentType = input => { + let config = {}; + + if (input.type === 'string') { + if (!input.multiline) { + if (input.secret) { + config._component = 'at-input-text'; + } else { + config._component = 'at-input-secret'; + } + } else { + config._expand = true; + + if (input.secret) { + config._component = 'at-input-textarea-secret'; + } else { + config._component = 'at-input-textarea'; + } + } + + if (input.format === 'ssh_private_key') { + config._format = 'ssh-key'; + } + } else if (input.type === 'number') { + config._component = 'at-input-number'; + } else if (input.choices) { + config._component = 'at-input-select'; + config._format = 'array'; + config._data = input.choices; + config._exp = 'index as choice for (index, choice) in state._data'; + } else { + throw new Error('Unsupported input type: ' + input.type) + } + + return config; + }; + vm.createElement = (input, index) => { let tabindex = Number(scope.tab) + index; + let col = input._expand ? 12 : scope.col; let element = - `<${input._component} col="${scope.col}" tab="${tabindex}" + `<${input._component} col="${col}" tab="${tabindex}" state="${state._reference}._group[${index}]"> `; @@ -91,19 +120,35 @@ function AtInputGroupController ($scope, $compile) { vm.insert = group => { let container = document.createElement('div'); - let divider = angular.element(`
`)[0]; + let col = 1; + let colPerRow = 12 / scope.col; + let isDivided = true; - for (let i = 0; i < group.length; i++) { - if (i !== 0 && (i % (12 / scope.col)) === 0) { - container.appendChild(divider); + group.forEach((input, i) => { + if (input._expand && !isDivided) { + container.appendChild(vm.createInputDivider()); } - container.appendChild(group[i]._element[0]); - } + container.appendChild(input._element[0]); + + if ((input._expand || col % colPerRow === 0) && i !== group.length -1) { + container.appendChild(vm.createInputDivider()); + isDivided = true; + col = 0; + } else { + isDivided = false; + } + + col++; + }); element.appendChild(container); }; + vm.createInputDivider = () => { + return angular.element(`
`)[0]; + }; + vm.compile = group => { group.forEach(component => $compile(component._element[0])(scope.$parent)); }; diff --git a/awx/ui/client/lib/components/input/select.directive.js b/awx/ui/client/lib/components/input/select.directive.js index 9a1b004654..b9e1b6588c 100644 --- a/awx/ui/client/lib/components/input/select.directive.js +++ b/awx/ui/client/lib/components/input/select.directive.js @@ -46,6 +46,16 @@ function AtInputSelectController (baseInputController, eventService) { scope.$on('$destroy', () => eventService.remove(listeners)); }; + + vm.updateDisplayModel = () => { + if (scope.state._format === 'array') { + scope.displayModel = scope.state._data[scope.state._value]; + } else if (scope.state._format === 'grouped-object') { + scope.displayModel = scope.state._value[scope.state._display]; + } else { + throw new Error('Unsupported display model type'); + } + }; } AtInputSelectController.$inject = ['BaseInputController', 'EventService']; diff --git a/awx/ui/client/lib/components/input/select.partial.html b/awx/ui/client/lib/components/input/select.partial.html index 5d2a6d9df8..da688c394b 100644 --- a/awx/ui/client/lib/components/input/select.partial.html +++ b/awx/ui/client/lib/components/input/select.partial.html @@ -6,8 +6,8 @@ @@ -15,6 +15,7 @@ ng-model="state._value" ng-attr-tabindex="{{ tab || undefined }}" ng-disabled="state._disabled || form.disabled" + ng-change="vm.updateDisplayModel()" ng-options="{{ state._exp }}"> diff --git a/awx/ui/client/lib/components/input/textarea-secret.directive.js b/awx/ui/client/lib/components/input/textarea-secret.directive.js new file mode 100644 index 0000000000..77f0f954a4 --- /dev/null +++ b/awx/ui/client/lib/components/input/textarea-secret.directive.js @@ -0,0 +1,44 @@ +function atInputTextareaSecretLink (scope, element, attrs, controllers) { + let formController = controllers[0]; + let inputController = controllers[1]; + + if (scope.tab === '1') { + element.find('input')[0].focus(); + } + + inputController.init(scope, element, formController); +} + +function AtInputTextareaSecretController (baseInputController) { + let vm = this || {}; + + vm.init = (scope, element, form) => { + baseInputController.call(vm, 'input', scope, element, form); + + vm.check(); + }; +} + +AtInputTextareaSecretController.$inject = ['BaseInputController']; + +function atInputTextareaSecret (pathService) { + return { + restrict: 'E', + transclude: true, + replace: true, + require: ['^^atForm', 'atInputTextareaSecret'], + templateUrl: pathService.getPartialPath('components/input/textarea-secret'), + controller: AtInputTextareaSecretController, + controllerAs: 'vm', + link: atInputTextareaSecretLink, + scope: { + state: '=', + col: '@', + tab: '@' + } + }; +} + +atInputTextareaSecret.$inject = ['PathService']; + +export default atInputTextareaSecret; diff --git a/awx/ui/client/lib/components/input/textarea-secret.partial.html b/awx/ui/client/lib/components/input/textarea-secret.partial.html new file mode 100644 index 0000000000..209becf4ac --- /dev/null +++ b/awx/ui/client/lib/components/input/textarea-secret.partial.html @@ -0,0 +1,16 @@ +
+
+ + + + + +
+
diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 489305f887..1f36c7c5c2 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -19,7 +19,7 @@ function createFormSchema (type, config) { function CredentialModel (method, id) { BaseModel.call(this, 'credentials'); - this.createFormSchema = createFormSchema; + this.createFormSchema = createFormSchema.bind(this); return this.request(method, id) .then(() => this); diff --git a/awx/ui/client/lib/models/CredentialType.js b/awx/ui/client/lib/models/CredentialType.js index 875ab8caf0..2679e191d7 100644 --- a/awx/ui/client/lib/models/CredentialType.js +++ b/awx/ui/client/lib/models/CredentialType.js @@ -29,8 +29,8 @@ function mergeInputProperties (type) { function CredentialTypeModel (method, id) { BaseModel.call(this, 'credential_types'); - this.categorizeByKind = categorizeByKind; - this.mergeInputProperties = mergeInputProperties; + this.categorizeByKind = categorizeByKind.bind(this); + this.mergeInputProperties = mergeInputProperties.bind(this); return this.request(method, id) .then(() => this);