mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 02:50:02 -03:30
Add component controller inheritance and new inputs
This commit is contained in:
parent
1644f78cf3
commit
effd537a9c
@ -12,7 +12,9 @@
|
||||
<at-input-text col="4" tab="2" state="vm.description"></at-input-text>
|
||||
<at-input-select col="4" tab="3" state="vm.kind"></at-input-select>
|
||||
|
||||
<at-dynamic-input-group col="4" tab="4" state="vm.dynamic"></at-dynamic-input-group>
|
||||
<at-dynamic-input-group col="4" tab="4" state="vm.dynamic">
|
||||
Type Details
|
||||
</at-dynamic-input-group>
|
||||
|
||||
<at-action-group col="12" pos="right">
|
||||
<at-form-action type="cancel"></at-form-action>
|
||||
|
||||
@ -1,12 +1,27 @@
|
||||
.at-DynamicInputGroup {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin: @at-space-6x 0 0 0;
|
||||
}
|
||||
|
||||
.at-DynamicInputGroup-inset {
|
||||
.at-DynamicInputGroup-border {
|
||||
position: absolute;
|
||||
width: @at-inset-width;
|
||||
height: 100%;
|
||||
background: @at-gray;
|
||||
left: -@at-inset-width;
|
||||
}
|
||||
|
||||
.at-DynamicInputGroup-title {
|
||||
.at-mixin-Heading(@at-font-size-2x);
|
||||
margin-top: 0;
|
||||
margin-left: @at-space-5x;
|
||||
margin-bottom: @at-space-4x;
|
||||
}
|
||||
|
||||
.at-DynamicInputGroup-divider {
|
||||
clear: both;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: @at-space-6x;
|
||||
}
|
||||
|
||||
@ -52,7 +52,9 @@ function AtDynamicInputGroupController ($scope, $compile) {
|
||||
|
||||
inputs.forEach((input, i) => {
|
||||
if (input.type === 'string') {
|
||||
if (input.secret) {
|
||||
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';
|
||||
@ -61,10 +63,9 @@ function AtDynamicInputGroupController ($scope, $compile) {
|
||||
}
|
||||
}
|
||||
|
||||
components.push({
|
||||
options: input,
|
||||
components.push(Object.assign({
|
||||
element: vm.createElement(input, i)
|
||||
});
|
||||
}, input));
|
||||
});
|
||||
|
||||
return components;
|
||||
@ -83,10 +84,15 @@ function AtDynamicInputGroupController ($scope, $compile) {
|
||||
|
||||
vm.insert = components => {
|
||||
let group = document.createElement('div');
|
||||
let divider = angular.element(`<div class="at-DynamicInputGroup-divider"></div>`)[0];
|
||||
|
||||
components.forEach(component => {
|
||||
group.appendChild(component.element[0]);
|
||||
});
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
if (i !== 0 && (i % (12 / scope.col)) === 0) {
|
||||
group.appendChild(divider);
|
||||
}
|
||||
|
||||
group.appendChild(components[i].element[0]);
|
||||
}
|
||||
|
||||
element.appendChild(group);
|
||||
};
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
<div class="col-sm-12 at-DynamicInputGroup">
|
||||
<div class="at-DynamicInputGroup-inset"></div>
|
||||
<div class="at-DynamicInputGroup-container"></div>
|
||||
<div ng-show="state.value" class="col-sm-12 at-DynamicInputGroup">
|
||||
<div class="at-DynamicInputGroup-border"></div>
|
||||
<div class="at-DynamicInputGroup-inset">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h4 class="at-DynamicInputGroup-title"><ng-transclude></ng-transclude></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="at-DynamicInputGroup-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -6,7 +6,9 @@ import formAction from './form/action.directive';
|
||||
import inputLabel from './input/label.directive';
|
||||
import inputSearch from './input/search.directive';
|
||||
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 panel from './panel/panel.directive';
|
||||
import panelHeading from './panel/heading.directive';
|
||||
import panelBody from './panel/body.directive';
|
||||
@ -14,6 +16,9 @@ import popover from './popover/popover.directive';
|
||||
import toggleButton from './toggle/button.directive';
|
||||
import toggleContent from './toggle/content.directive';
|
||||
|
||||
import BaseInputController from './input/base.controller';
|
||||
|
||||
|
||||
angular
|
||||
.module('at.lib.components', [])
|
||||
.directive('atActionGroup', actionGroup)
|
||||
@ -23,13 +28,16 @@ angular
|
||||
.directive('atFormAction', formAction)
|
||||
.directive('atInputLabel', inputLabel)
|
||||
.directive('atInputSearch', inputSearch)
|
||||
.directive('atInputSecret', inputSecret)
|
||||
.directive('atInputSelect', inputSelect)
|
||||
.directive('atInputText', inputText)
|
||||
.directive('atInputTextarea', inputTextarea)
|
||||
.directive('atPanel', panel)
|
||||
.directive('atPanelHeading', panelHeading)
|
||||
.directive('atPanelBody', panelBody)
|
||||
.directive('atPopover', popover)
|
||||
.directive('atToggleButton', toggleButton)
|
||||
.directive('atToggleContent', toggleContent);
|
||||
.directive('atToggleContent', toggleContent)
|
||||
.service('BaseInputController', BaseInputController);
|
||||
|
||||
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
}
|
||||
|
||||
.at-InputLabel {
|
||||
}
|
||||
|
||||
.at-InputLabel-name {
|
||||
color: @at-gray-dark-4x;
|
||||
font-size: @at-font-size-2x;
|
||||
font-weight: @at-font-weight;
|
||||
|
||||
38
awx/ui/client/lib/components/input/base.controller.js
Normal file
38
awx/ui/client/lib/components/input/base.controller.js
Normal file
@ -0,0 +1,38 @@
|
||||
function BaseInputController () {
|
||||
return function extend (type, scope, form) {
|
||||
let vm = this;
|
||||
|
||||
scope.state = scope.state || {};
|
||||
|
||||
scope.state.required = scope.state.required || false;
|
||||
scope.state.isValid = scope.state.isValid || false;
|
||||
scope.state.disabled = scope.state.disabled || false;
|
||||
|
||||
form.use(type, scope);
|
||||
|
||||
vm.validate = () => {
|
||||
let isValid = true;
|
||||
|
||||
if (scope.state.required && !scope.state.value) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (scope.state.validate && !scope.state.validate(scope.state.value)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
};
|
||||
|
||||
vm.check = () => {
|
||||
let isValid = vm.validate();
|
||||
|
||||
if (isValid !== scope.state.isValid) {
|
||||
scope.state.isValid = isValid;
|
||||
form.check();
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default BaseInputController;
|
||||
@ -1,5 +1,5 @@
|
||||
<label class="at-InputLabel at-u-flat">
|
||||
<span ng-if="config.options.required" class="pull-left at-InputLabel-required">*</span>
|
||||
<span class="pull-left">{{::state.options.label}}</span>
|
||||
<span ng-if="state.required" class="pull-left at-InputLabel-required">*</span>
|
||||
<span class="pull-left at-InputLabel-name">{{::state.label}}</span>
|
||||
<at-popover class="pull-left" state="state"></at-popover>
|
||||
</label>
|
||||
|
||||
44
awx/ui/client/lib/components/input/secret.directive.js
Normal file
44
awx/ui/client/lib/components/input/secret.directive.js
Normal file
@ -0,0 +1,44 @@
|
||||
function atInputSecretLink (scope, el, attrs, controllers) {
|
||||
let formController = controllers[0];
|
||||
let inputController = controllers[1];
|
||||
|
||||
if (scope.tab === '1') {
|
||||
el.find('input')[0].focus();
|
||||
}
|
||||
|
||||
inputController.init(scope, formController);
|
||||
}
|
||||
|
||||
function AtInputSecretController (baseInputController) {
|
||||
let vm = this || {};
|
||||
|
||||
vm.init = (scope, form) => {
|
||||
baseInputController.call(vm, 'input', scope, form);
|
||||
|
||||
vm.check();
|
||||
};
|
||||
}
|
||||
|
||||
AtInputSecretController.$inject = ['BaseInputController'];
|
||||
|
||||
function atInputSecret (pathService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
require: ['^^atForm', 'atInputSecret'],
|
||||
templateUrl: pathService.getPartialPath('components/input/secret'),
|
||||
controller: AtInputSecretController,
|
||||
controllerAs: 'vm',
|
||||
link: atInputSecretLink,
|
||||
scope: {
|
||||
state: '=',
|
||||
col: '@',
|
||||
tab: '@'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
atInputSecret.$inject = ['PathService'];
|
||||
|
||||
export default atInputSecret;
|
||||
10
awx/ui/client/lib/components/input/secret.partial.html
Normal file
10
awx/ui/client/lib/components/input/secret.partial.html
Normal file
@ -0,0 +1,10 @@
|
||||
<div class="col-sm-{{::col}}">
|
||||
<div class="form-group at-u-flat">
|
||||
<at-input-label state="state"></at-input-label>
|
||||
<input type="text" class="form-control at-Input" ng-model="state.value"
|
||||
ng-attr-maxlength="{{ state.options.max_length || undefined }}"
|
||||
ng-attr-tabindex="{{ tab || undefined }}"
|
||||
ng-attr-placeholder="{{::state.placeholder || undefined }}"
|
||||
ng-change="vm.check()" ng-disabled="state.disabled" />
|
||||
</div>
|
||||
</div>
|
||||
@ -13,27 +13,19 @@ function atInputSelectLink (scope, el, attrs, controllers) {
|
||||
inputController.init(formController, scope, elements);
|
||||
}
|
||||
|
||||
function AtInputSelectController (eventService) {
|
||||
function AtInputSelectController (baseInputController, eventService) {
|
||||
let vm = this || {};
|
||||
|
||||
let scope;
|
||||
let state;
|
||||
let form;
|
||||
let input;
|
||||
let select;
|
||||
|
||||
vm.init = (_form_, _scope_, elements) => {
|
||||
form = _form_;
|
||||
vm.init = (form, _scope_, elements) => {
|
||||
baseInputController.call(vm, 'input', _scope_, form);
|
||||
|
||||
scope = _scope_;
|
||||
input = elements.input;
|
||||
select = elements.select;
|
||||
scope = _scope_;
|
||||
state = scope.state || {};
|
||||
|
||||
state.required = state.required || false;
|
||||
state.isValid = state.isValid || false;
|
||||
state.disabled = state.disabled || false;
|
||||
|
||||
form.use('input', scope);
|
||||
|
||||
vm.setListeners();
|
||||
vm.check();
|
||||
@ -56,32 +48,9 @@ function AtInputSelectController (eventService) {
|
||||
|
||||
scope.$on('$destroy', () => eventService.remove(listeners));
|
||||
};
|
||||
|
||||
vm.validate = () => {
|
||||
let isValid = true;
|
||||
|
||||
if (state.required && !state.value) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (state.validate && !state.validate(state.value)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
};
|
||||
|
||||
vm.check = () => {
|
||||
let isValid = vm.validate();
|
||||
|
||||
if (isValid !== state.isValid) {
|
||||
state.isValid = isValid;
|
||||
form.check();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AtInputSelectController.$inject = ['EventService'];
|
||||
AtInputSelectController.$inject = ['BaseInputController', 'EventService'];
|
||||
|
||||
function atInputSelect (pathService) {
|
||||
return {
|
||||
|
||||
@ -6,54 +6,21 @@ function atInputTextLink (scope, el, attrs, controllers) {
|
||||
el.find('input')[0].focus();
|
||||
}
|
||||
|
||||
inputController.init(formController, scope);
|
||||
inputController.init(scope, formController);
|
||||
}
|
||||
|
||||
function AtInputTextController () {
|
||||
function AtInputTextController (baseInputController) {
|
||||
let vm = this || {};
|
||||
|
||||
let scope;
|
||||
let state;
|
||||
let form;
|
||||
|
||||
vm.init = (_form_, _scope_) => {
|
||||
form = _form_;
|
||||
scope = _scope_;
|
||||
state = scope.state || {};
|
||||
|
||||
state.required = state.required || false;
|
||||
state.isValid = state.isValid || false;
|
||||
state.disabled = state.disabled || false;
|
||||
|
||||
form.use('input', scope);
|
||||
vm.init = (scope, form) => {
|
||||
baseInputController.call(vm, 'input', scope, form);
|
||||
|
||||
vm.check();
|
||||
};
|
||||
|
||||
vm.validate = () => {
|
||||
let isValid = true;
|
||||
|
||||
if (state.required && !state.value) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (state.validate && !state.validate(state.value)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
};
|
||||
|
||||
vm.check = () => {
|
||||
let isValid = vm.validate();
|
||||
|
||||
if (isValid !== state.isValid) {
|
||||
state.isValid = isValid;
|
||||
form.check();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
AtInputTextController.$inject = ['BaseInputController'];
|
||||
|
||||
function atInputText (pathService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
|
||||
44
awx/ui/client/lib/components/input/textarea.directive.js
Normal file
44
awx/ui/client/lib/components/input/textarea.directive.js
Normal file
@ -0,0 +1,44 @@
|
||||
function atInputTextareaLink (scope, el, attrs, controllers) {
|
||||
let formController = controllers[0];
|
||||
let inputController = controllers[1];
|
||||
|
||||
if (scope.tab === '1') {
|
||||
el.find('input')[0].focus();
|
||||
}
|
||||
|
||||
inputController.init(scope, formController);
|
||||
}
|
||||
|
||||
function AtInputTextareaController (baseInputController) {
|
||||
let vm = this || {};
|
||||
|
||||
vm.init = (scope, form) => {
|
||||
baseInputController.call(vm, 'input', scope, form);
|
||||
|
||||
vm.check();
|
||||
};
|
||||
}
|
||||
|
||||
AtInputTextareaController.$inject = ['BaseInputController'];
|
||||
|
||||
function atInputTextarea (pathService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
replace: true,
|
||||
require: ['^^atForm', 'atInputTextarea'],
|
||||
templateUrl: pathService.getPartialPath('components/input/textarea'),
|
||||
controller: AtInputTextareaController,
|
||||
controllerAs: 'vm',
|
||||
link: atInputTextareaLink,
|
||||
scope: {
|
||||
state: '=',
|
||||
col: '@',
|
||||
tab: '@'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
atInputTextarea.$inject = ['PathService'];
|
||||
|
||||
export default atInputTextarea;
|
||||
11
awx/ui/client/lib/components/input/textarea.partial.html
Normal file
11
awx/ui/client/lib/components/input/textarea.partial.html
Normal file
@ -0,0 +1,11 @@
|
||||
<div class="col-sm-{{::col}}">
|
||||
<div class="form-group at-u-flat">
|
||||
<at-input-label state="state"></at-input-label>
|
||||
<textarea class="form-control at-Input" ng-model="state.value"
|
||||
ng-attr-maxlength="{{ state.options.max_length || undefined }}"
|
||||
ng-attr-tabindex="{{ tab || undefined }}"
|
||||
ng-attr-placeholder="{{::state.placeholder || undefined }}"
|
||||
ng-change="vm.check()" ng-disabled="state.disabled" />
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -20,11 +20,5 @@
|
||||
}
|
||||
|
||||
.at-Panel-headingTitle {
|
||||
color: @at-gray-dark-4x;
|
||||
font-size: @at-font-size-3x;
|
||||
font-weight: @at-font-weight-2x;
|
||||
line-height: @at-line-height-short;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.at-mixin-Heading(@at-font-size-3x);
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
border-radius: @at-border-radius;
|
||||
box-shadow: 0 5px 10px rgba(0,0,0, 0.2);
|
||||
transition: opacity .15s linear;
|
||||
font-weight: @at-font-weight
|
||||
}
|
||||
|
||||
.at-Popover-arrow {
|
||||
|
||||
@ -1,64 +1,91 @@
|
||||
let pathService;
|
||||
|
||||
function link (scope, el, attr) {
|
||||
function atPopoverLink (scope, el, attr, controllers) {
|
||||
let popoverController = controllers[0];
|
||||
let icon = el[0];
|
||||
let popover = icon.getElementsByClassName('at-Popover-container')[0];
|
||||
|
||||
el.on('click', createDisplayListener(icon, popover));
|
||||
popoverController.init(icon, popover);
|
||||
}
|
||||
|
||||
function createDisplayListener (icon, popover) {
|
||||
return event => {
|
||||
let arrow = popover.getElementsByClassName('at-Popover-arrow')[0];
|
||||
function AtPopoverController () {
|
||||
let vm = this;
|
||||
|
||||
let iPos = icon.getBoundingClientRect();
|
||||
let pPos = popover.getBoundingClientRect();
|
||||
let icon;
|
||||
let popover;
|
||||
|
||||
let wHeight = window.clientHeight;
|
||||
let pHeight = pPos.height;
|
||||
vm.init = (_icon_, _popover_) => {
|
||||
icon = _icon_;
|
||||
popover = _popover_;
|
||||
|
||||
let cx = Math.floor(iPos.left + (iPos.width / 2));
|
||||
let cy = Math.floor(iPos.top + (iPos.height / 2));
|
||||
icon.addEventListener('click', vm.createDisplayListener());
|
||||
};
|
||||
|
||||
if (cy < (pHeight / 2)) {
|
||||
popover.style.top = '10px';
|
||||
} else {
|
||||
popover.style.top = (cy - pHeight / 2) + 'px';
|
||||
}
|
||||
vm.createDismissListener = (createEvent) => {
|
||||
return event => {
|
||||
event.stopPropagation();
|
||||
|
||||
popover.style.left = cx + 'px';
|
||||
vm.open = false;
|
||||
|
||||
arrow.style.top = iPos.top + 'px';
|
||||
arrow.style.left = iPos.left + 20 + 'px';
|
||||
popover.style.visibility = 'hidden';
|
||||
popover.style.opacity = 0;
|
||||
|
||||
popover.style.visibility = 'visible';
|
||||
popover.style.opacity = 1;
|
||||
window.removeEventListener('click', vm.dismissListener);
|
||||
window.removeEventListener('resize', vm.dismissListener);
|
||||
};
|
||||
};
|
||||
|
||||
let dismissListener = createDismissListener(popover);
|
||||
vm.createDisplayListener = () => {
|
||||
return event => {
|
||||
if (vm.open) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('mousedown', dismissListener);
|
||||
window.addEventListener('resize', dismissListener);
|
||||
event.stopPropagation();
|
||||
|
||||
vm.open = true;
|
||||
|
||||
let arrow = popover.getElementsByClassName('at-Popover-arrow')[0];
|
||||
|
||||
let iPos = icon.getBoundingClientRect();
|
||||
let pPos = popover.getBoundingClientRect();
|
||||
|
||||
let wHeight = window.clientHeight;
|
||||
let pHeight = pPos.height;
|
||||
|
||||
let cx = Math.floor(iPos.left + (iPos.width / 2));
|
||||
let cy = Math.floor(iPos.top + (iPos.height / 2));
|
||||
|
||||
if (cy < (pHeight / 2)) {
|
||||
popover.style.top = '10px';
|
||||
} else {
|
||||
popover.style.top = (cy - pHeight / 2) + 'px';
|
||||
}
|
||||
|
||||
popover.style.left = cx + 'px';
|
||||
|
||||
arrow.style.top = iPos.top + 'px';
|
||||
arrow.style.left = iPos.left + 20 + 'px';
|
||||
|
||||
popover.style.visibility = 'visible';
|
||||
popover.style.opacity = 1;
|
||||
|
||||
vm.dismissListener = vm.createDismissListener(event);
|
||||
|
||||
window.addEventListener('click', vm.dismissListener);
|
||||
window.addEventListener('resize', vm.dismissListener);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function createDismissListener (popover) {
|
||||
return function dismissListener () {
|
||||
popover.style.visibility = 'hidden';
|
||||
popover.style.opacity = 0;
|
||||
|
||||
window.removeEventListener('mousedown', dismissListener);
|
||||
};
|
||||
}
|
||||
|
||||
function atPopover (_pathService_) {
|
||||
pathService = _pathService_;
|
||||
|
||||
function atPopover (pathService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
require: ['atPopover'],
|
||||
templateUrl: pathService.getPartialPath('components/popover/popover'),
|
||||
link,
|
||||
controller: AtPopoverController,
|
||||
controllerAs: 'vm',
|
||||
link: atPopoverLink,
|
||||
scope: {
|
||||
state: '='
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div ng-show="state.options.help_text" class="at-Popover">
|
||||
<div ng-show="state.help_text" class="at-Popover">
|
||||
<span class="at-Popover-icon">
|
||||
<i class="fa fa-question-circle"></i>
|
||||
</span>
|
||||
@ -6,6 +6,6 @@
|
||||
<div class="at-Popover-arrow">
|
||||
<i class="fa fa-caret-left fa-2x"></i>
|
||||
</div>
|
||||
<div class="at-Popover-content">{{::state.options.help_text}}</div>
|
||||
<div class="at-Popover-content">{{::state.help_text}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,6 +10,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.at-mixin-Heading (@size) {
|
||||
color: @at-gray-dark-4x;
|
||||
font-size: @size;
|
||||
font-weight: @at-font-weight-2x;
|
||||
line-height: @at-line-height-short;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.at-mixin-Button () {
|
||||
padding: @at-space-2x @at-space-4x;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user