Merge pull request #147 from glyptodon/generalize-fields

GUAC-1106: Create generalized form module and directives
This commit is contained in:
James Muehlner
2015-04-15 19:51:05 -07:00
16 changed files with 560 additions and 330 deletions

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* A directive that allows editing of a collection of fields.
*/
angular.module('form').directive('guacForm', [function form() {
return {
// Element only
restrict: 'E',
replace: true,
scope: {
/**
* The translation namespace of the translation strings that will
* be generated for all fields. This namespace is absolutely
* required. If this namespace is omitted, all generated
* translation strings will be placed within the MISSING_NAMESPACE
* namespace, as a warning.
*
* @type String
*/
namespace : '=',
/**
* The fields to display.
*
* @type Field[]
*/
fields : '=',
/**
* The object which will receive all field values. Each field value
* will be assigned to the property of this object having the same
* name.
*
* @type Object.<String, String>
*/
model : '='
},
templateUrl: 'app/form/templates/form.html',
controller: ['$scope', '$injector', function formController($scope, $injector) {
// Required services
var translationStringService = $injector.get('translationStringService');
/**
* The object which will receive all field values. Normally, this
* will be the object provided within the "model" attribute. If
* no such object has been provided, a blank model will be used
* instead as a placeholder, such that the fields of this form
* will have something to bind to.
*
* @type Object.<String, String>
*/
$scope.values = {};
/**
* Produces the translation string for the header of the given
* field. The translation string will be of the form:
*
* <code>NAMESPACE.FIELD_HEADER_NAME<code>
*
* where <code>NAMESPACE</code> is the namespace provided to the
* directive and <code>NAME</code> is the field name transformed
* via translationStringService.canonicalize().
*
* @param {Field} field
* The field for which to produce the translation string.
*
* @returns {String}
* The translation string which produces the translated header
* of the field.
*/
$scope.getFieldHeader = function getFieldHeader(field) {
return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE')
+ '.FIELD_HEADER_' + translationStringService.canonicalize(field.name);
};
// Update string value and re-assign to model when field is changed
$scope.$watch('model', function setModel(model) {
// Assign new model only if provided
if (model)
$scope.values = model;
// Otherwise, use blank model
else
$scope.values = {};
});
}] // end controller
};
}]);

View File

@@ -0,0 +1,225 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* A directive that allows editing of a field.
*/
angular.module('form').directive('guacFormField', [function formField() {
return {
// Element only
restrict: 'E',
replace: true,
scope: {
/**
* The translation namespace of the translation strings that will
* be generated for this field. This namespace is absolutely
* required. If this namespace is omitted, all generated
* translation strings will be placed within the MISSING_NAMESPACE
* namespace, as a warning.
*
* @type String
*/
namespace : '=',
/**
* The field to display.
*
* @type Field
*/
field : '=',
/**
* The property which contains this fields current value. When this
* field changes, the property will be updated accordingly.
*
* @type String
*/
model : '='
},
templateUrl: 'app/form/templates/formField.html',
controller: ['$scope', '$injector', function formFieldController($scope, $injector) {
// Required services
var translationStringService = $injector.get('translationStringService');
/**
* The type to use for password input fields. By default, password
* input fields have type 'password', and are thus masked.
*
* @type String
* @default 'password'
*/
$scope.passwordInputType = 'password';
/**
* Returns a string which describes the action the next call to
* togglePassword() will have.
*
* @return {String}
* A string which describes the action the next call to
* togglePassword() will have.
*/
$scope.getTogglePasswordHelpText = function getTogglePasswordHelpText() {
// If password is hidden, togglePassword() will show the password
if ($scope.passwordInputType === 'password')
return 'FORM.HELP_SHOW_PASSWORD';
// If password is shown, togglePassword() will hide the password
return 'FORM.HELP_HIDE_PASSWORD';
};
/**
* Toggles visibility of the field contents, if this field is a
* password field. Initially, password contents are masked
* (invisible).
*/
$scope.togglePassword = function togglePassword() {
// If password is hidden, show the password
if ($scope.passwordInputType === 'password')
$scope.passwordInputType = 'text';
// If password is shown, hide the password
else
$scope.passwordInputType = 'password';
};
/**
* Produces the translation string for the given field option
* value. The translation string will be of the form:
*
* <code>NAMESPACE.FIELD_OPTION_NAME_VALUE<code>
*
* where <code>NAMESPACE</code> is the namespace provided to the
* directive, <code>NAME</code> is the field name transformed
* via translationStringService.canonicalize(), and
* <code>VALUE</code> is the option value transformed via
* translationStringService.canonicalize()
*
* @param {String} value
* The name of the option value.
*
* @returns {String}
* The translation string which produces the translated name of the
* value specified.
*/
$scope.getFieldOption = function getFieldOption(value) {
// Don't bother if the model is not yet defined
if (!$scope.field)
return '';
return translationStringService.canonicalize($scope.namespace || 'MISSING_NAMESPACE')
+ '.FIELD_OPTION_' + translationStringService.canonicalize($scope.field.name)
+ '_' + translationStringService.canonicalize(value || 'EMPTY');
};
/**
* Translates the given string field value into an appropriately-
* typed value as dictated by the attributes of the field,
* exposing that typed value within the scope as
* <code>$scope.typedValue<code>.
*
* @param {String} modelValue
* The current string value of the field.
*/
var setTypedValue = function setTypedValue(modelValue) {
// Don't bother if the model is not yet defined
if (!$scope.field)
return;
// Coerce numeric strings to numbers
if ($scope.field.type === 'NUMERIC')
$scope.typedValue = (modelValue ? Number($scope.field.value) : null);
// Coerce boolean strings to boolean values
else if ($scope.field.type === 'BOOLEAN')
$scope.typedValue = (modelValue === $scope.field.value);
// All other field types are represented internally as strings
else
$scope.typedValue = modelValue || '';
};
/**
* Translates the given typed field value into a string as dictated
* by the attributes of the field, assigning that string value to
* the model.
*
* @param {String|Number|Boolean} typedValue
* The current value of the field, as an appropriate JavaScript
* type.
*/
var setModelValue = function setModelValue(typedValue) {
// Don't bother if the model is not yet defined
if (!$scope.field)
return;
// Convert numeric values back into strings
if ($scope.field.type === 'NUMERIC') {
if (!typedValue)
$scope.model = '';
else
$scope.model = typedValue.toString();
}
// Convert boolean values back into strings based on field description
else if ($scope.field.type === 'BOOLEAN')
$scope.model = (typedValue ? $scope.field.value : '');
// All other field types are already strings
else
$scope.model = typedValue || '';
};
// Update string value and re-assign to model when field is changed
$scope.$watch('field', function setField(field) {
setTypedValue($scope.model);
setModelValue($scope.typedValue);
});
// Update typed value when model is changed
$scope.$watch('model', function setModel(model) {
setTypedValue(model);
});
// Update string value in model when typed value is changed
$scope.$watch('typedValue', function typedValueChanged(typedValue) {
setModelValue(typedValue);
});
}] // end controller
};
}]);

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Module for displaying dynamic forms.
*/
angular.module('form', ['locale']);

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/* Keep toggle-password icon on same line */
.form-field .password-field {
white-space: nowrap;
}
/* Generic 1x1em icon/button */
.form-field .password-field .icon.toggle-password {
display: inline-block;
opacity: 0.5;
cursor: default;
background-repeat: no-repeat;
background-size: 1em;
width: 1em;
height: 1em;
}
/* Icon for unmasking passwords */
.form-field .password-field input[type=password] ~ .icon.toggle-password {
background-image: url('images/action-icons/guac-show-pass.png');
}
/* Icon for masking passwords */
.form-field .password-field input[type=text] ~ .icon.toggle-password {
background-image: url('images/action-icons/guac-hide-pass.png');
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2015 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
table.form th {
text-align: left;
font-weight: normal;
padding-right: 1em;
}

View File

@@ -0,0 +1,32 @@
<table class="form">
<!--
Copyright 2015 Glyptodon LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<!-- All fields in form -->
<tr ng-repeat="field in fields">
<th>{{getFieldHeader(field) | translate}}</th>
<td>
<guac-form-field namespace="namespace" field="field" model="values[field.name]"></guac-form-field>
</td>
</tr>
</table>

View File

@@ -1,4 +1,4 @@
<div class="connection-parameter">
<div class="form-field">
<!--
Copyright 2014 Glyptodon LLC.
@@ -22,21 +22,21 @@
-->
<!-- Generic input types -->
<input ng-show="parameter.type === 'TEXT'" type="text" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input ng-show="parameter.type === 'NUMERIC'" type="number" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input ng-show="parameter.type === 'USERNAME'" type="text" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input ng-show="parameter.type === 'BOOLEAN'" type="checkbox" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input ng-show="field.type === 'TEXT'" type="text" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input ng-show="field.type === 'NUMERIC'" type="number" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input ng-show="field.type === 'USERNAME'" type="text" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<input ng-show="field.type === 'BOOLEAN'" type="checkbox" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<!-- Password parameter -->
<div ng-show="parameter.type === 'PASSWORD'" class="password-field">
<!-- Password field -->
<div ng-show="field.type === 'PASSWORD'" class="password-field">
<input type="{{passwordInputType}}" ng-model="typedValue" autocorrect="off" autocapitalize="off"/>
<div class="icon toggle-password" ng-click="togglePassword()" title="{{getTogglePasswordHelpText() | translate}}"></div>
</div>
<!-- Multiline parameter -->
<textarea ng-show="parameter.type === 'MULTILINE'" ng-model="typedValue" autocorrect="off" autocapitalize="off"></textarea>
<!-- Multiline field -->
<textarea ng-show="field.type === 'MULTILINE'" ng-model="typedValue" autocorrect="off" autocapitalize="off"></textarea>
<!-- Enumerated parameter -->
<select ng-show="parameter.type === 'ENUM'" ng-model="typedValue" ng-options="option.value as getProtocolParameterOption(protocol.name, parameter.name, option.value) | translate for option in parameter.options | orderBy: value"></select>
<!-- Enumerated field -->
<select ng-show="field.type === 'ENUM'" ng-model="typedValue" ng-options="option.value as getFieldOption(option.value) | translate for option in field.options | orderBy: value"></select>
</div>

View File

@@ -266,9 +266,41 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
$scope.parameters = {};
}
/**
* Returns the translation string namespace for the protocol having the
* given name. The namespace will be of the form:
*
* <code>PROTOCOL_NAME</code>
*
* where <code>NAME</code> is the protocol name transformed via
* translationStringService.canonicalize().
*
* @param {String} protocolName
* The name of the protocol.
*
* @returns {String}
* The translation namespace for the protocol specified, or null if no
* namespace could be generated.
*/
$scope.getNamespace = function getNamespace(protocolName) {
// Do not generate a namespace if no protocol is selected
if (!protocolName)
return null;
return 'PROTOCOL_' + translationStringService.canonicalize(protocolName);
};
/**
* Given the internal name of a protocol, produces the translation string
* for the localized version of that protocol's name.
* for the localized version of that protocol's name. The translation
* string will be of the form:
*
* <code>NAMESPACE.NAME<code>
*
* where <code>NAMESPACE</code> is the namespace generated from
* $scope.getNamespace().
*
* @param {String} protocolName
* The name of the protocol.
@@ -278,27 +310,7 @@ angular.module('manage').controller('manageConnectionController', ['$scope', '$i
* protocol specified.
*/
$scope.getProtocolName = function getProtocolName(protocolName) {
return 'PROTOCOL_' + translationStringService.canonicalize(protocolName) + '.NAME';
};
/**
* Given the internal name of a protocol and the internal name of a
* parameter for that protocol, produces the translation string
* for the localized, human-readable name of that protocol parameter.
*
* @param {String} protocolName
* The name of the protocol.
*
* @param {String} parameterName
* The name of the protocol parameter.
*
* @returns {String}
* The translation string which produces the translated name of the
* protocol parameter specified.
*/
$scope.getProtocolParameterName = function getProtocolParameterName(protocolName, parameterName) {
return 'PROTOCOL_' + translationStringService.canonicalize(protocolName)
+ '.FIELD_HEADER_' + translationStringService.canonicalize(parameterName);
return $scope.getNamespace(protocolName) + '.NAME';
};
/**

View File

@@ -1,232 +0,0 @@
/*
* Copyright (C) 2014 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* A directive that allows editing of a connection parameter.
*/
angular.module('manage').directive('guacConnectionParameter', [function connectionParameter() {
return {
// Element only
restrict: 'E',
replace: true,
scope: {
/**
* The protocol this parameter is associated with.
*
* @type Protocol
*/
protocol : '=',
/**
* The unique name of this parameter within the protocol
* definition.
*
* @type String
*/
name : '=',
/**
* The current map of parameter names to their corresponding string
* values.
*
* @type Object.<String, String>
*/
parameters : '='
},
templateUrl: 'app/manage/templates/connectionParameter.html',
controller: ['$scope', '$injector', function connectionParameterController($scope, $injector) {
// Required services
var $q = $injector.get('$q');
var translationStringService = $injector.get('translationStringService');
/**
* The type to use for password input fields. By default, password
* input fields have type 'password', and are thus masked.
*
* @type String
* @default 'password'
*/
$scope.passwordInputType = 'password';
/**
* Returns a string which describes the action the next call to
* togglePassword() will have.
*
* @return {String}
* A string which describes the action the next call to
* togglePassword() will have.
*/
$scope.getTogglePasswordHelpText = function getTogglePasswordHelpText() {
// If password is hidden, togglePassword() will show the password
if ($scope.passwordInputType === 'password')
return 'MANAGE.HELP_SHOW_PASSWORD';
// If password is shown, togglePassword() will hide the password
return 'MANAGE.HELP_HIDE_PASSWORD';
};
/**
* Toggles visibility of the parameter contents, if this parameter
* is a password parameter. Initially, password contents are
* masked (invisible).
*/
$scope.togglePassword = function togglePassword() {
// If password is hidden, show the password
if ($scope.passwordInputType === 'password')
$scope.passwordInputType = 'text';
// If password is shown, hide the password
else
$scope.passwordInputType = 'password';
};
/**
* Deferred load of the parameter definition, pending availability
* of the protocol definition as a whole.
*
* @type Deferred
*/
var parameterDefinitionAvailable = $q.defer();
/**
* Populates the parameter definition on the scope as
* <code>$scope.parameter</code> if both the parameter name and
* protocol definition are available. If either are unavailable,
* this function has no effect.
*/
var retrieveParameterDefinition = function retrieveParameterDefinition() {
// Both name and protocol are needed to retrieve the parameter definition
if (!$scope.name || !$scope.protocol)
return;
// Once protocol definition is available, locate parameter definition by name
$scope.protocol.parameters.forEach(function findParameter(parameter) {
if (parameter.name === $scope.name) {
$scope.parameter = parameter;
parameterDefinitionAvailable.resolve(parameter);
}
});
};
// Load parameter definition once protocol definition is available.
$scope.$watch('name', retrieveParameterDefinition);
$scope.$watch('protocol', retrieveParameterDefinition);
// Update typed value when parameter set is changed
$scope.$watch('parameters', function setParameters(parameters) {
// Don't bother if no parameters were provided
if (!parameters)
return;
// Wait for parameter definition
parameterDefinitionAvailable.promise.then(function setTypedValue() {
// Pull parameter value
var value = parameters[$scope.name];
// Coerce numeric strings to numbers
if ($scope.parameter.type === 'NUMERIC')
$scope.typedValue = (value ? Number(value) : null);
// Coerce boolean strings to boolean values
else if ($scope.parameter.type === 'BOOLEAN')
$scope.typedValue = (value === $scope.parameter.value);
// All other parameter types are represented internally as strings
else
$scope.typedValue = value || '';
});
});
// Update string value in parameter set when typed value is changed
$scope.$watch('typedValue', function typedValueChanged(typedValue) {
// Don't bother if there's nothing to set
if (!$scope.parameters)
return;
// Wait for parameter definition
parameterDefinitionAvailable.promise.then(function setValue() {
// Convert numeric values back into strings
if ($scope.parameter.type === 'NUMERIC') {
if (!typedValue)
$scope.parameters[$scope.name] = '';
else
$scope.parameters[$scope.name] = typedValue.toString();
}
// Convert boolean values back into strings based on protocol description
else if ($scope.parameter.type === 'BOOLEAN')
$scope.parameters[$scope.name] = (typedValue ? $scope.parameter.value : '');
// All other parameter types are already strings
else
$scope.parameters[$scope.name] = typedValue || '';
});
}); // end watch typedValue
/**
* Given the internal name of a protocol, the internal name of a
* parameter for that protocol, and the internal name for a valid
* value of that parameter, produces the translation string for the
* localized, human-readable name of that parameter value.
*
* @param {String} protocolName
* The name of the protocol.
*
* @param {String} parameterName
* The name of the protocol parameter.
*
* @param {String} parameterValue
* The name of the parameter value.
*
* @returns {String}
* The translation string which produces the translated name of the
* parameter value specified.
*/
$scope.getProtocolParameterOption = function getProtocolParameterOption(protocolName, parameterName, parameterValue) {
return 'PROTOCOL_' + translationStringService.canonicalize(protocolName)
+ '.FIELD_OPTION_' + translationStringService.canonicalize(parameterName)
+ '_' + translationStringService.canonicalize(parameterValue || 'EMPTY');
};
}] // end controller
};
}]);

View File

@@ -24,6 +24,7 @@
* The module for the administration functionality.
*/
angular.module('manage', [
'form',
'groupList',
'list',
'locale',

View File

@@ -21,37 +21,8 @@
*/
/* Do not stretch connection parameters to fit available area */
.connection-parameter input[type=text],
.connection-parameter input[type=password],
.connection-parameter input[type=number] {
.connection-parameters input[type=text],
.connection-parameters input[type=password],
.connection-parameters input[type=number] {
width: auto;
}
/* Keep toggle-password icon on same line */
.connection-parameter .password-field {
white-space: nowrap;
}
/* Generic 1x1em icon/button */
.connection-parameter .password-field .icon.toggle-password {
display: inline-block;
opacity: 0.5;
cursor: default;
background-repeat: no-repeat;
background-size: 1em;
width: 1em;
height: 1em;
}
/* Icon for unmasking passwords */
.connection-parameter .password-field input[type=password] ~ .icon.toggle-password {
background-image: url('images/action-icons/guac-show-pass.png');
}
/* Icon for masking passwords */
.connection-parameter .password-field input[type=text] ~ .icon.toggle-password {
background-image: url('images/action-icons/guac-hide-pass.png');
}

View File

@@ -59,17 +59,10 @@ THE SOFTWARE.
<!-- Connection parameters -->
<h2 class="header">{{'MANAGE_CONNECTION.SECTION_HEADER_PARAMETERS' | translate}}</h2>
<div class="section" ng-class="{loading: !parameters}">
<table class="properties">
<!-- All the different possible editable field types -->
<tr ng-repeat="parameter in protocols[connection.protocol].parameters">
<th>{{getProtocolParameterName(connection.protocol, parameter.name) | translate}}</th>
<td>
<guac-connection-parameter protocol="protocols[connection.protocol]" name="parameter.name" parameters="parameters"></guac-connection-parameter>
</td>
</tr>
</table>
<div class="section connection-parameters" ng-class="{loading: !parameters}">
<guac-form namespace="getNamespace(connection.protocol)"
fields="protocols[connection.protocol].parameters"
model="parameters"></guac-form>
</div>
<!-- Form action buttons -->

View File

@@ -21,20 +21,20 @@
*/
/**
* Service which defines the ProtocolParameter class.
* Service which defines the Field class.
*/
angular.module('rest').factory('ProtocolParameter', [function defineProtocolParameter() {
angular.module('rest').factory('Field', [function defineField() {
/**
* The object returned by REST API calls when representing the data
* associated with a configuration parameter of a remote desktop protocol.
* associated with a field or configuration parameter.
*
* @constructor
* @param {ProtocolParameter|Object} [template={}]
* @param {Field|Object} [template={}]
* The object whose properties should be copied within the new
* ProtocolParameter.
* Field.
*/
var ProtocolParameter = function ProtocolParameter(template) {
var Field = function Field(template) {
// Use empty object by default
template = template || {};
@@ -56,12 +56,12 @@ angular.module('rest').factory('ProtocolParameter', [function defineProtocolPara
/**
* The type string defining which values this parameter may contain,
* as well as what properties are applicable. Valid types are listed
* within ProtocolParameter.Type.
* within Field.Type.
*
* @type String
* @default ProtocolParameter.Type.TEXT
* @default Field.Type.TEXT
*/
this.type = template.type || ProtocolParameter.Type.TEXT;
this.type = template.type || Field.Type.TEXT;
/**
* The value to set the parameter to, in the case of a BOOLEAN
@@ -75,7 +75,7 @@ angular.module('rest').factory('ProtocolParameter', [function defineProtocolPara
* All possible legal values for this parameter. This property is only
* applicable to ENUM type parameters.
*
* @type ProtocolParameterOption[]
* @type FieldOption[]
*/
this.options = template.options;
@@ -84,7 +84,7 @@ angular.module('rest').factory('ProtocolParameter', [function defineProtocolPara
/**
* All valid protocol parameter types.
*/
ProtocolParameter.Type = {
Field.Type = {
/**
* The type string associated with parameters that may contain a single
@@ -147,6 +147,6 @@ angular.module('rest').factory('ProtocolParameter', [function defineProtocolPara
};
return ProtocolParameter;
return Field;
}]);

View File

@@ -21,20 +21,20 @@
*/
/**
* Service which defines the ProtocolParameterOption class.
* Service which defines the FieldOption class.
*/
angular.module('rest').factory('ProtocolParameterOption', [function defineProtocolParameterOption() {
angular.module('rest').factory('FieldOption', [function defineFieldOption() {
/**
* The object returned by REST API calls when representing a single possible
* legal value of a configuration parameter of a remote desktop protocol.
* legal value of a field.
*
* @constructor
* @param {ProtocolParameterOption|Object} [template={}]
* @param {FieldOption|Object} [template={}]
* The object whose properties should be copied within the new
* ProtocolParameterOption.
* FieldOption.
*/
var ProtocolParameterOption = function ProtocolParameterOption(template) {
var FieldOption = function FieldOption(template) {
// Use empty object by default
template = template || {};
@@ -56,6 +56,6 @@ angular.module('rest').factory('ProtocolParameterOption', [function defineProtoc
};
return ProtocolParameterOption;
return FieldOption;
}]);

View File

@@ -57,7 +57,7 @@ angular.module('rest').factory('Protocol', [function defineProtocol() {
* An array of all known parameters for this protocol, their types,
* and other information.
*
* @type ProtocolParameter[]
* @type Field[]
* @default []
*/
this.parameters = template.parameters || [];

View File

@@ -117,6 +117,13 @@
},
"FORM" : {
"HELP_SHOW_PASSWORD" : "Click to show password",
"HELP_HIDE_PASSWORD" : "Click to hide password"
},
"HOME" : {
"INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT",
@@ -161,8 +168,6 @@
"FORMAT_HISTORY_START" : "@:APP.FORMAT_DATE_TIME_PRECISE",
"HELP_CONNECTIONS" : "Click or tap on a connection below to manage that connection. Depending on your access level, connections can be added and deleted, and their properties (protocol, hostname, port, etc.) can be changed.",
"HELP_SHOW_PASSWORD" : "Click to show password",
"HELP_HIDE_PASSWORD" : "Click to hide password",
"INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT",
"INFO_CONNECTION_DURATION_UNKNOWN" : "--",