GUACAMOLE-630: Do not contain entire field nor entire header within <label>.

Enclosing the entire field within the <label> results in problems when
the field is large and when the field contains multiple interactive
elements. Clicking within interactive elements of a complex field
triggers the <label>, refocusing the first input element. If the field
is large, the <label> will contain empty space which also refocuses the
input field upon being clicked, despite appearing to be the background
of the page.
This commit is contained in:
Michael Jumper
2019-08-06 23:18:50 -07:00
parent a1a8be6fe5
commit 038f5ba0c5
16 changed files with 65 additions and 17 deletions

View File

@@ -35,6 +35,6 @@ angular.module('form').controller('textFieldController', ['$scope', '$injector',
// Generate unique ID for datalist, if applicable
if ($scope.field.options && $scope.field.options.length)
$scope.dataListId = $scope.field.name + '-datalist';
$scope.dataListId = $scope.fieldId + '-datalist';
}]);

View File

@@ -72,6 +72,18 @@ angular.module('form').directive('guacFormField', [function formField() {
*/
var fieldContent = $element.find('.form-field');
/**
* An ID value which is reasonably likely to be unique relative to
* other elements on the page. This ID should be used to associate
* the relevant input element with the label provided by the
* guacFormField directive, if there is such an input element.
*
* @type String
*/
$scope.fieldId = 'guac-field-XXXXXXXXXXXXXXXX'.replace(/X/g, function getRandomCharacter() {
return Math.floor(Math.random() * 36).toString(36);
}) + '-' + new Date().getTime().toString(36);
/**
* Produces the translation string for the header of the field with
* the given name. If no name is supplied, then the name of the

View File

@@ -234,6 +234,11 @@ angular.module('form').provider('formService', function formServiceProvider() {
* A String which defines the unique namespace associated the
* translation strings used by the form using a field of this type.
*
* fieldId:
* A String value which is reasonably likely to be unique and may
* be used to associate the main element of the field with its
* label.
*
* field:
* The Field object that is being rendered, representing a field of
* this type.

View File

@@ -1 +1,5 @@
<input type="checkbox" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input type="checkbox"
ng-attr-id="{{ fieldId }}"
ng-model="typedValue"
autocorrect="off"
autocapitalize="off"/>

View File

@@ -1,5 +1,6 @@
<div class="date-field">
<input type="date"
ng-attr-id="{{ fieldId }}"
ng-model="typedValue"
ng-model-options="modelOptions"
guac-lenient-date

View File

@@ -1,5 +1,6 @@
<div class="email-field">
<input type="email"
ng-attr-id="{{ fieldId }}"
ng-model="model"
ng-hide="readOnly"
autocorrect="off"

View File

@@ -1,9 +1,11 @@
<label class="labeled-field" ng-class="{empty: !model}" ng-show="isFieldVisible()">
<div class="labeled-field" ng-class="{empty: !model}" ng-show="isFieldVisible()">
<!-- Field header -->
<span class="field-header">{{getFieldHeader() | translate}}</span>
<div class="field-header">
<label ng-attr-for="{{ fieldId }}">{{getFieldHeader() | translate}}</label>
</div>
<!-- Field content -->
<div class="form-field"></div>
</label>
</div>

View File

@@ -1 +1,3 @@
<select ng-model="model" ng-options="language.key as language.value for language in languages | toArray | orderBy: key"></select>
<select ng-attr-id="{{ fieldId }}"
ng-model="model"
ng-options="language.key as language.value for language in languages | toArray | orderBy: key"></select>

View File

@@ -1 +1,5 @@
<input type="number" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input type="number"
ng-attr-id="{{ fieldId }}"
ng-model="typedValue"
autocorrect="off"
autocapitalize="off"/>

View File

@@ -1,4 +1,9 @@
<div class="password-field">
<input type="{{passwordInputType}}" ng-model="model" ng-trim="false" autocorrect="off" autocapitalize="off"/>
<input type="{{passwordInputType}}"
ng-attr-id="{{ fieldId }}"
ng-model="model"
ng-trim="false"
autocorrect="off"
autocapitalize="off"/>
<div class="icon toggle-password" ng-click="togglePassword()" title="{{getTogglePasswordHelpText() | translate}}"></div>
</div>

View File

@@ -1 +1,3 @@
<select ng-model="model" ng-options="option as getFieldOption(option) | translate for option in field.options | orderBy: value"></select>
<select ng-attr-id="{{ fieldId }}"
ng-model="model"
ng-options="option as getFieldOption(option) | translate for option in field.options | orderBy: value"></select>

View File

@@ -1,5 +1,5 @@
<div class="terminal-color-scheme-field">
<select ng-model="selectedColorScheme">
<select ng-attr-id="{{ fieldId }}" ng-model="selectedColorScheme">
<option ng-repeat="option in field.options | orderBy: value"
ng-value="option">{{ getFieldOption(option) | translate }}</option>
<option value="custom">{{ getFieldOption('custom') | translate }}</option>

View File

@@ -1 +1,4 @@
<textarea ng-model="model" autocorrect="off" autocapitalize="off"></textarea>
<textarea ng-attr-id="{{ fieldId }}"
ng-model="model"
autocorrect="off"
autocapitalize="off"></textarea>

View File

@@ -1,6 +1,11 @@
<div class="text-field">
<input type="text" ng-model="model" autocorrect="off" autocapitalize="off" ng-attr-list="{{ dataListId }}"/>
<datalist ng-if="dataListId" id="{{ dataListId }}">
<input type="text"
ng-attr-id="{{ fieldId }}"
ng-attr-list="{{ dataListId }}"
ng-model="model"
autocorrect="off"
autocapitalize="off"/>
<datalist ng-if="dataListId" ng-attr-id="{{ dataListId }}">
<option ng-repeat="option in field.options | orderBy: option"
value="{{ option }}">{{ getFieldOption(option) | translate }}</option>
</datalist>

View File

@@ -1,5 +1,6 @@
<div class="time-field">
<input type="time"
ng-attr-id="{{ fieldId }}"
ng-model="typedValue"
ng-model-options="modelOptions"
guac-lenient-time

View File

@@ -2,6 +2,7 @@
<!-- Available time zone regions -->
<select class="time-zone-region"
ng-attr-id="{{ fieldId }}"
ng-model="region"
ng-options="name for name in regions | orderBy: name"></select>