From dd143c1c715135d0387a448d37ba318e1767c62d Mon Sep 17 00:00:00 2001 From: Joe Fiorini Date: Thu, 6 Aug 2015 10:27:29 -0400 Subject: [PATCH] Use select lists for multiple choice answers --- awx/ui/client/src/helpers/JobSubmission.js | 50 ++++++++--------- .../src/job-templates/survey-maker/main.js | 2 + .../questions/finalize.factory.js | 36 +++++++----- .../job-templates/survey-maker/render/main.js | 9 +++ .../render/multiple-choice.directive.js | 56 +++++++++++++++++++ .../render/multiple-choice.partial.html | 5 ++ .../render/multiselect.directive.js | 42 ++++++++++++++ .../render/survey-question.directive.js | 50 +++++++++++++++++ .../render/survey-question.partial.html | 7 +++ .../shared/survey-controls.block.less | 11 ++++ .../survey-maker/surveys/init.factory.js | 6 +- .../survey-maker/surveys/show.factory.js | 3 + awx/ui/client/src/shared/Modal.js | 4 +- 13 files changed, 234 insertions(+), 47 deletions(-) create mode 100644 awx/ui/client/src/job-templates/survey-maker/render/main.js create mode 100644 awx/ui/client/src/job-templates/survey-maker/render/multiple-choice.directive.js create mode 100644 awx/ui/client/src/job-templates/survey-maker/render/multiple-choice.partial.html create mode 100644 awx/ui/client/src/job-templates/survey-maker/render/multiselect.directive.js create mode 100644 awx/ui/client/src/job-templates/survey-maker/render/survey-question.directive.js create mode 100644 awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html create mode 100644 awx/ui/client/src/job-templates/survey-maker/shared/survey-controls.block.less diff --git a/awx/ui/client/src/helpers/JobSubmission.js b/awx/ui/client/src/helpers/JobSubmission.js index 345183ab82..d461d6a659 100644 --- a/awx/ui/client/src/helpers/JobSubmission.js +++ b/awx/ui/client/src/helpers/JobSubmission.js @@ -507,14 +507,14 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm, url = params.url, callback=params.callback, scope = params.scope, - i, j, + i, requiredAsterisk, requiredClasses, defaultValue, choices, element, minlength, maxlength, - checked, min, max, + min, max, survey_url = GetBasePath('job_templates') + id + '/survey_spec/' ; //for toggling the input on password inputs @@ -605,41 +605,35 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm, if(question.type === 'multiplechoice'){ choices = question.choices.split(/\n/); element = (question.type==="multiselect") ? "checkbox" : 'radio'; - question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; - html+='
'; - for( j = 0; j' + - ''+choices[j] +'
' ; + + if (question.default) { + scope[question.variable] = question.default; + } else { + scope[question.variable] = ''; } - html+= '
Please select an answer.
'+ - '
'; + + html+='
'; + html += ''; + // html+= '
Please select an answer.
'+ + // '
'; html+= '
'; //end survey_taker_input } if(question.type === "multiselect"){ //seperate the choices out into an array choices = question.choices.split(/\n/); - question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; //ensure that the default answers are in an array - scope[question.variable] = question.default.split(/\n/); - //create a new object to be used by the surveyCheckboxes directive - scope[question.variable + '_object'] = { - name: question.variable, - value: (question.default.split(/\n/)[0]==="") ? [] : question.default.split(/\n/) , - required: question.required, - options:[] - }; - //load the options into the 'options' key of the new object - for(j=0; j'+ - '{{job_launch_form.'+question.variable+'_object.$error.checkbox}}'+ - '
Please select at least one answer.
'; + //create a new object to be used by the surveyCheckboxes directive + html += ''; + // html += ''+ + // '{{job_launch_form.'+question.variable+'_object.$error.checkbox}}'+ + // '
Please select at least one answer.
'; } if(question.type === 'integer'){ diff --git a/awx/ui/client/src/job-templates/survey-maker/main.js b/awx/ui/client/src/job-templates/survey-maker/main.js index 38d30679d9..d6ac9de3b8 100644 --- a/awx/ui/client/src/job-templates/survey-maker/main.js +++ b/awx/ui/client/src/job-templates/survey-maker/main.js @@ -1,6 +1,7 @@ import listGenerator from '../../shared/list-generator/main'; import questions from './questions/main'; import surveys from './surveys/main'; +import render from './render/main'; import shared from './shared/main'; export default @@ -8,5 +9,6 @@ export default [ listGenerator.name, questions.name, surveys.name, + render.name, shared.name ]); diff --git a/awx/ui/client/src/job-templates/survey-maker/questions/finalize.factory.js b/awx/ui/client/src/job-templates/survey-maker/questions/finalize.factory.js index 9c81ec8d37..facd87260b 100644 --- a/awx/ui/client/src/job-templates/survey-maker/questions/finalize.factory.js +++ b/awx/ui/client/src/job-templates/survey-maker/questions/finalize.factory.js @@ -18,13 +18,9 @@ export default index = params.index, required, element, - choices, - i, - checked, max, min, defaultValue, - answers, html = ""; question.index = index; @@ -65,18 +61,28 @@ export default '
'; } if(question.type === 'multiplechoice' || question.type === "multiselect"){ - choices = question.choices.split(/\n/); - element = (question.type==="multiselect") ? "checkbox" : 'radio'; - question.default = (question.default) ? question.default : (question.default_multiselect) ? question.default_multiselect : "" ; - answers = question.default.split(/\n/); - html += '
'; - for( i = 0; i' + - ''+choices[i] +'
' ; + + question.default = question.default_multiselect || question.default; + + var defaultScopePropertyName = + question.variable + '_default'; + + if (question.default) { + if (question.type === 'multiselect' && typeof question.default.split === 'function') { + scope[defaultScopePropertyName] = question.default.split('\n'); + } else if (question.type !== 'multiselect') { + scope[defaultScopePropertyName] = question.default; + } + } else { + scope[defaultScopePropertyName] = ''; } + + html += '
'; + html += '
'; + html += '
'; + html += ''; + html += '
'; + html += '
'; html += '
'; } diff --git a/awx/ui/client/src/job-templates/survey-maker/render/main.js b/awx/ui/client/src/job-templates/survey-maker/render/main.js new file mode 100644 index 0000000000..90390c74e1 --- /dev/null +++ b/awx/ui/client/src/job-templates/survey-maker/render/main.js @@ -0,0 +1,9 @@ +import surveyQuestion from './survey-question.directive'; +import multipleChoice from './multiple-choice.directive'; +import multiSelect from './multiselect.directive'; + +export default + angular.module('jobTemplates.surveyMaker.render', []) + .directive('surveyQuestion', surveyQuestion) + .directive('multipleChoice', multipleChoice) + .directive('multiSelect', multiSelect); diff --git a/awx/ui/client/src/job-templates/survey-maker/render/multiple-choice.directive.js b/awx/ui/client/src/job-templates/survey-maker/render/multiple-choice.directive.js new file mode 100644 index 0000000000..aaa36ac053 --- /dev/null +++ b/awx/ui/client/src/job-templates/survey-maker/render/multiple-choice.directive.js @@ -0,0 +1,56 @@ +/* jshint unused: vars */ +import {templateUrl} from '../../../shared/template-url/template-url.factory'; + +function link($timeout, scope, element, attrs, ngModel) { + attrs.width = attrs.width || '100%'; + + $timeout(function() { + + $.fn.select2.amd.require( + [ 'select2/utils', + 'select2/dropdown', + 'select2/dropdown/search', + 'select2/dropdown/attachContainer', + 'select2/dropdown/closeOnSelect', + 'select2/dropdown/minimumResultsForSearch' + ], + function(Utils, Dropdown, Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch) { + + var CustomAdapter = + _.reduce([Search, AttachContainer, CloseOnSelect, MinimumResultsForSearch], + function(Adapter, Decorator) { + return Utils.Decorate(Adapter, Decorator); + }, Dropdown); + + element.find('select').select2( + { multiple: scope.isMultipleSelect(), + minimumResultsForSearch: Infinity, + theme: 'bootstrap', + width: attrs.width, + dropdownAdapter: CustomAdapter + }); + }); + + }); + +} + +export default + [ '$timeout', + function($timeout) { + var directive = + { restrict: 'E', + require: 'ngModel', + scope: { + isMultipleSelect: '&multiSelect', + choices: '=', + question: '=', + isRequired: '=ngRequired', + selectedValue: '=ngModel' + }, + templateUrl: templateUrl('job-templates/survey-maker/render/multiple-choice'), + link: _.partial(link, $timeout) + }; + return directive; + } + ]; diff --git a/awx/ui/client/src/job-templates/survey-maker/render/multiple-choice.partial.html b/awx/ui/client/src/job-templates/survey-maker/render/multiple-choice.partial.html new file mode 100644 index 0000000000..57557c76ca --- /dev/null +++ b/awx/ui/client/src/job-templates/survey-maker/render/multiple-choice.partial.html @@ -0,0 +1,5 @@ +
+ +
diff --git a/awx/ui/client/src/job-templates/survey-maker/render/multiselect.directive.js b/awx/ui/client/src/job-templates/survey-maker/render/multiselect.directive.js new file mode 100644 index 0000000000..dd3fb60610 --- /dev/null +++ b/awx/ui/client/src/job-templates/survey-maker/render/multiselect.directive.js @@ -0,0 +1,42 @@ +/* jshint unused: vars */ + +/** + * @ngdoc + * + * @name jobTemplates.surveyMaker.render.multiSelect + * @description + * Angular provides no method of binding to "multiple" for + * select lists. This is because under normal circumstances, + * the structure of `ng-model` changes based on whether `multiple` + * is true or false. We're not needing to "bind" to "multiple", + * but we do need to pass in the value dynamically. This allows + * us to do that. + */ + +var directive = + { require: 'ngModel', + compile: function() { + return { + pre: function(scope, element, attrs, ngModel) { + if (_.isUndefined(scope.isMultipleSelect)) { + return; + } + + if (!scope.isMultipleSelect()) { + return; + } + + element.attr('multiple', true); + attrs.multiple = true; + + + } + }; + }, + priority: 1000 + }; + +export default + function() { + return directive; + } diff --git a/awx/ui/client/src/job-templates/survey-maker/render/survey-question.directive.js b/awx/ui/client/src/job-templates/survey-maker/render/survey-question.directive.js new file mode 100644 index 0000000000..3dc64e6667 --- /dev/null +++ b/awx/ui/client/src/job-templates/survey-maker/render/survey-question.directive.js @@ -0,0 +1,50 @@ +/* jshint unused: vars */ +import {templateUrl} from '../../../shared/template-url/template-url.factory'; + +/** + * @ngdoc + * @name jobTemplates.surveyMaker.render.surveyQuestion + * @description + * Directive that will eventually hold all logic + * for rendering different form controls based on + * the question type for a survey. + */ + +// Since we're generating HTML for the entire survey, and _then_ +// calling $compile, this directive never actually gets compiled +// with the question object we need. Therefore, we give it the index +// of the question as an attribute (not scope) and then look it up +// in the `survey_questions` by that index when it the directive gets +// compiled. +// +function findQuestionByIndex(questions, index) { + return _.find(questions, function(question) { + return question.index === index; + }); +} + +function link(scope, element, attrs) { + var question = findQuestionByIndex(scope.surveyQuestions, Number(attrs.index)); + + scope.question = question; + + if (!_.isUndefined(question.choices)) { + scope.choices = question.choices.split('\n'); + } +} + +export default + function() { + var directive = + { restrict: 'E', + scope: + { surveyQuestions: '=', + selectedValue: '=ngModel', + isRequired: '@ngRequired' + }, + templateUrl: templateUrl('job-templates/survey-maker/render/survey-question'), + link: link + }; + + return directive; + } diff --git a/awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html b/awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html new file mode 100644 index 0000000000..1b761f17e5 --- /dev/null +++ b/awx/ui/client/src/job-templates/survey-maker/render/survey-question.partial.html @@ -0,0 +1,7 @@ + + diff --git a/awx/ui/client/src/job-templates/survey-maker/shared/survey-controls.block.less b/awx/ui/client/src/job-templates/survey-maker/shared/survey-controls.block.less new file mode 100644 index 0000000000..74b3af4967 --- /dev/null +++ b/awx/ui/client/src/job-templates/survey-maker/shared/survey-controls.block.less @@ -0,0 +1,11 @@ +/** @define SurveyControls */ + +.SurveyControls { + &-selectWrapper { + margin-left: 15px; + } + &--dropdown { + z-index: 10000; + opacity: 1; + } +} diff --git a/awx/ui/client/src/job-templates/survey-maker/surveys/init.factory.js b/awx/ui/client/src/job-templates/survey-maker/surveys/init.factory.js index b710fc0a92..9f0cf8ea1f 100644 --- a/awx/ui/client/src/job-templates/survey-maker/surveys/init.factory.js +++ b/awx/ui/client/src/job-templates/survey-maker/surveys/init.factory.js @@ -136,13 +136,13 @@ export default if(questions.length>0){ $('#survey-save-button').removeAttr('disabled'); } - scope.finalizeQuestion(questions[key], key); + scope.finalizeQuestion(questions[key], Number(key)); } else if(scope.mode=== 'edit' ){ if(scope.survey_questions.length>0 && scope.can_edit === true){ $('#survey-save-button').removeAttr('disabled'); } - scope.finalizeQuestion(scope.survey_questions[key] , key); + scope.finalizeQuestion(scope.survey_questions[key] , Number(key)); } } }; @@ -464,7 +464,7 @@ export default scope.survey_questions[key] = data; } $('#'+elementID).empty(); - scope.finalizeQuestion(data , key); + scope.finalizeQuestion(data , Number(key)); } diff --git a/awx/ui/client/src/job-templates/survey-maker/surveys/show.factory.js b/awx/ui/client/src/job-templates/survey-maker/surveys/show.factory.js index 74b0b7fd1c..84874522cb 100644 --- a/awx/ui/client/src/job-templates/survey-maker/surveys/show.factory.js +++ b/awx/ui/client/src/job-templates/survey-maker/surveys/show.factory.js @@ -70,6 +70,9 @@ export default } }, + _allowInteraction: function(e) { + return !!$(e.target).is('.select2-input') || this._super(e); + }, callback: callback }); }; diff --git a/awx/ui/client/src/shared/Modal.js b/awx/ui/client/src/shared/Modal.js index 772ba532af..2c17b1e14c 100644 --- a/awx/ui/client/src/shared/Modal.js +++ b/awx/ui/client/src/shared/Modal.js @@ -55,6 +55,7 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper']) onResizeStop = params.onResizeStop, onClose = params.onClose, onOpen = params.onOpen, + _allowInteraction = params._allowInteraction, callback = params.callback, beforeDestroy = params.beforeDestroy, closeOnEscape = (params.closeOnEscape === undefined) ? false : params.closeOnEscape, @@ -177,7 +178,8 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper']) if (onOpen) { onOpen(); } - } + }, + _allowInteraction: _allowInteraction }); }; }])