From bf42021a322b1528cfa030dd9b6c32cf329c52ba Mon Sep 17 00:00:00 2001 From: gconsidine Date: Thu, 27 Jul 2017 16:47:23 -0400 Subject: [PATCH] Add search to models w/ search on lookup input --- .../credentials/add-credentials.controller.js | 5 +- .../edit-credentials.controller.js | 19 ++-- awx/ui/client/features/credentials/index.js | 23 +++-- .../lib/components/input/base.controller.js | 24 ++++-- .../lib/components/input/lookup.directive.js | 81 ++++++++++++----- .../lib/components/input/lookup.partial.html | 2 +- awx/ui/client/lib/models/Base.js | 86 +++++++++++++++---- awx/ui/client/lib/models/Credential.js | 5 -- 8 files changed, 172 insertions(+), 73 deletions(-) diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 79fa9afd0c..e1f925a60b 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -31,10 +31,9 @@ function AddCredentialsController (models, $state, strings) { vm.form.inputs = { _get: id => { - let type = credentialType.graft(id); - type.mergeInputProperties(); + credentialType.mergeInputProperties(); - return type.get('inputs.fields'); + return credentialType.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index e49dfe51ce..8c2c79626a 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -5,7 +5,6 @@ function EditCredentialsController (models, $state, $scope, strings) { let credential = models.credential; let credentialType = models.credentialType; let organization = models.organization; - let selectedCredentialType = models.selectedCredentialType; vm.mode = 'edit'; vm.strings = strings; @@ -50,21 +49,19 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.credential_type._resource = 'credential_type'; vm.form.credential_type._model = credentialType; vm.form.credential_type._route = 'credentials.edit.credentialType'; - vm.form.credential_type._value = selectedCredentialType.get('id'); - vm.form.credential_type._displayValue = selectedCredentialType.get('name'); + vm.form.credential_type._value = credentialType.get('id'); + vm.form.credential_type._displayValue = credentialType.get('name'); vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); vm.form.inputs = { _get (id) { - let type = credentialType.graft(id); - - type.mergeInputProperties(); + credentialType.mergeInputProperties(); - if (type.get('id') === credential.get('credential_type')) { - return credential.assignInputGroupValues(type.get('inputs.fields')); + if (credentialType.get('id') === credential.get('credential_type')) { + return credential.assignInputGroupValues(credentialType.get('inputs.fields')); } - return type.get('inputs.fields'); + return credentialType.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', @@ -73,8 +70,8 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.save = data => { data.user = me.getSelf().id; - credential.clearTypeInputs(); - + credential.unset('inputs'); + return credential.request('put', data); }; diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js index 077e9562e1..9e0662cdcd 100644 --- a/awx/ui/client/features/credentials/index.js +++ b/awx/ui/client/features/credentials/index.js @@ -7,13 +7,13 @@ function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, O let id = $stateParams.credential_id; let promises = { - me: new Me('get'), - credentialType: new CredentialType('get'), - organization: new Organization('get') + me: new Me('get') }; if (!id) { promises.credential = new Credential('options'); + promises.credentialType = new CredentialType(); + promises.organization = new Organization(); return $q.all(promises) } @@ -22,10 +22,21 @@ function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, O return $q.all(promises) .then(models => { - let credentialTypeId = models.credential.get('credential_type'); - models.selectedCredentialType = models.credentialType.graft(credentialTypeId); + let typeId = models.credential.get('credential_type'); + let orgId = models.credential.get('organization'); - return models; + let dependents = { + credentialType: new CredentialType('get', typeId), + organization: new Organization('get', orgId) + }; + + return $q.all(dependents) + .then(related => { + models.credentialType = related.credentialType; + models.organization = related.organization; + + return models; + }); }); } diff --git a/awx/ui/client/lib/components/input/base.controller.js b/awx/ui/client/lib/components/input/base.controller.js index 43a382210f..8d92711660 100644 --- a/awx/ui/client/lib/components/input/base.controller.js +++ b/awx/ui/client/lib/components/input/base.controller.js @@ -67,16 +67,22 @@ function BaseInputController (strings) { }; }; - vm.check = () => { - let result = vm.validate(); - - if (scope.state._touched || !scope.state._required) { - scope.state._rejected = !result.isValid; - scope.state._isValid = result.isValid; - scope.state._message = result.message; - - form.check(); + vm.updateValidationState = result => { + if (!scope.state._touched && scope.state._required) { + return; } + + scope.state._rejected = !result.isValid; + scope.state._isValid = result.isValid; + scope.state._message = result.message; + + form.check(); + }; + + vm.check = result => { + result = result || vm.validate(); + + vm.updateValidationState(result); }; vm.toggleRevertReplace = () => { diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 362547a9d9..792709035b 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -1,3 +1,6 @@ +const DEFAULT_DEBOUNCE = 250; +const DEFAULT_KEY = 'name'; + function atInputLookupLink (scope, element, attrs, controllers) { let formController = controllers[0]; let inputController = controllers[1]; @@ -9,28 +12,40 @@ function atInputLookupLink (scope, element, attrs, controllers) { inputController.init(scope, element, formController); } -function AtInputLookupController (baseInputController, $state, $stateParams) { +function AtInputLookupController (baseInputController, $q, $state, $stateParams) { let vm = this || {}; let scope; + let model; + let search; vm.init = (_scope_, element, form) => { baseInputController.call(vm, 'input', _scope_, element, form); scope = _scope_; + model = scope.state._model; + scope.state._debounce = scope.state._debounce || DEFAULT_DEBOUNCE; + search = scope.state._search || { + key: DEFAULT_KEY, + config: { + unique: true + } + }; scope.$watch(scope.state._resource, vm.watchResource); - scope.state._validate = vm.checkOnInput; vm.check(); }; vm.watchResource = () => { + if (!scope[scope.state._resource]) { + return; + } + if (scope[scope.state._resource] !== scope.state._value) { - scope.state._value = scope[scope.state._resource]; scope.state._displayValue = scope[`${scope.state._resource}_name`]; - vm.check(); + vm.search(); } }; @@ -49,32 +64,54 @@ function AtInputLookupController (baseInputController, $state, $stateParams) { scope[scope.state._resource] = undefined; }; - vm.checkOnInput = () => { - if (!scope.state._touched) { - return { isValid: true }; + vm.searchAfterDebounce = () => { + vm.isDebouncing = true; + + vm.debounce = window.setTimeout(() => { + vm.isDebouncing = false; + vm.search(); + }, scope.state._debounce); + }; + + vm.resetDebounce = () => { + clearTimeout(vm.debounce); + vm.searchAfterDebounce(); + }; + + vm.search = () => { + return model.search({ [search.key]: scope.state._displayValue }, search.config) + .then(found => { + if (!found) { + return vm.reset(); + } + + scope[scope.state._resource] = model.get('id'); + scope.state._value = model.get('id'); + scope.state._displayValue = model.get('name'); + }) + .catch(() => vm.reset()) + .finally(() => { + let isValid = scope.state._value !== undefined; + let message = isValid ? '' : vm.strings.get('lookup.NOT_FOUND'); + + vm.check({ isValid, message }); + }); + }; + + vm.searchOnInput = () => { + if (vm.isDebouncing) { + return vm.resetDebounce(); } - let result = scope.state._model.match('get', 'name', scope.state._displayValue); + scope.state._touched = true; - if (result) { - scope[scope.state._resource] = result.id; - scope.state._value = result.id; - scope.state._displayValue = result.name; - - return { isValid: true }; - } - - vm.reset(); - - return { - isValid: false, - message: vm.strings.get('lookup.NOT_FOUND') - }; + vm.searchAfterDebounce(); }; } AtInputLookupController.$inject = [ 'BaseInputController', + '$q', '$state', '$stateParams' ]; diff --git a/awx/ui/client/lib/components/input/lookup.partial.html b/awx/ui/client/lib/components/input/lookup.partial.html index 39900afcc1..21ebf03b5d 100644 --- a/awx/ui/client/lib/components/input/lookup.partial.html +++ b/awx/ui/client/lib/components/input/lookup.partial.html @@ -16,7 +16,7 @@ ng-model="state._displayValue" ng-attr-tabindex="{{ tab || undefined }}" ng-attr-placeholder="{{::state._placeholder || undefined }}" - ng-change="vm.check()" + ng-change="vm.searchOnInput()" ng-disabled="state._disabled || form.disabled" /> diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index 0a4cef54fd..fea1fbd413 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -11,16 +11,41 @@ function request (method, resource) { return this.http[method](resource); } -function httpGet (resource) { - this.method = this.method || 'GET'; - +function search (params, config) { let req = { - method: this.method, + method: 'GET', + url: this.path, + params + }; + + return $http(req) + .then(res => { + if (!res.data.count) { + return false; + } + + if (config.unique) { + if (res.data.count !== 1) { + return false; + } + + this.model.GET = res.data.results[0]; + } else { + this.model.GET = res.data; + } + + return true; + }); +} + +function httpGet (resource) { + let req = { + method: 'GET', url: this.path }; if (typeof resource === 'object') { - this.model[this.method] = resource; + this.model.GET = resource; return $q.resolve(); } else if (resource) { @@ -43,9 +68,9 @@ function httpPost (data) { }; return $http(req).then(res => { - this.model.GET = res.data; + this.model.GET = res.data; - return res; + return res; }); } @@ -87,6 +112,28 @@ function get (keys) { return this.find('get', keys); } +function unset (method, keys) { + if (!keys) { + keys = method; + method = 'GET'; + } + + method = method.toUpperCase(); + keys = keys.split('.'); + + if (!keys.length) { + delete this.model[method]; + } else if (keys.length === 1) { + delete this.model[method][keys[0]]; + } else { + let property = keys.splice(-1); + keys = keys.join('.'); + + let model = this.find(method, keys) + delete model[property]; + } +} + function set (method, keys, value) { if (!value) { value = keys; @@ -189,6 +236,10 @@ function graft (id) { } function create (method, resource, graft) { + if (!method) { + return this; + } + this.promise = this.request(method, resource); if (graft) { @@ -200,21 +251,24 @@ function create (method, resource, graft) { } function BaseModel (path) { - this.model = {}; - this.get = get; - this.set = set; - this.options = options; - this.find = find; - this.match = match; - this.normalizePath = normalizePath; - this.graft = graft; this.create = create; + this.find = find; + this.get = get; + this.graft = graft; + this.match = match; + this.model = {}; + this.normalizePath = normalizePath; + this.options = options; this.request = request; + this.search = search; + this.set = set; + this.unset = unset; + this.http = { get: httpGet.bind(this), options: httpOptions.bind(this), post: httpPost.bind(this), - put: httpPut.bind(this) + put: httpPut.bind(this), }; this.path = this.normalizePath(path); diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 9937ebe281..0599513b84 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -33,17 +33,12 @@ function assignInputGroupValues (inputs) { }); } -function clearTypeInputs () { - delete this.model.GET.inputs; -} - function CredentialModel (method, resource, graft) { BaseModel.call(this, 'credentials'); this.Constructor = CredentialModel; this.createFormSchema = createFormSchema.bind(this); this.assignInputGroupValues = assignInputGroupValues.bind(this); - this.clearTypeInputs = clearTypeInputs.bind(this); return this.create(method, resource, graft); }