blob: 4198c10adc40e3f7443286d3b379bf06f99e0b34 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* A service for maintaining form-related metadata and linking that data to
* corresponding controllers and templates.
*/
angular.module('form').provider('formService', function formServiceProvider() {
/**
* Reference to the provider itself.
*
* @type formServiceProvider
*/
var provider = this;
/**
* Map of all registered field type definitions by name.
*
* @type Object.<String, FieldType>
*/
this.fieldTypes = {
/**
* Text field type.
*
* @see {@link Field.Type.TEXT}
* @type FieldType
*/
'TEXT' : {
module : 'form',
controller : 'textFieldController',
templateUrl : 'app/form/templates/textField.html'
},
/**
* Email address field type.
*
* @see {@link Field.Type.EMAIL}
* @type FieldType
*/
'EMAIL' : {
templateUrl : 'app/form/templates/emailField.html'
},
/**
* Numeric field type.
*
* @see {@link Field.Type.NUMERIC}
* @type FieldType
*/
'NUMERIC' : {
module : 'form',
controller : 'numberFieldController',
templateUrl : 'app/form/templates/numberField.html'
},
/**
* Boolean field type.
*
* @see {@link Field.Type.BOOLEAN}
* @type FieldType
*/
'BOOLEAN' : {
module : 'form',
controller : 'checkboxFieldController',
templateUrl : 'app/form/templates/checkboxField.html'
},
/**
* Username field type. Identical in principle to a text field, but may
* have different semantics.
*
* @see {@link Field.Type.USERNAME}
* @type FieldType
*/
'USERNAME' : {
templateUrl : 'app/form/templates/textField.html'
},
/**
* Password field type. Similar to a text field, but the contents of
* the field are masked.
*
* @see {@link Field.Type.PASSWORD}
* @type FieldType
*/
'PASSWORD' : {
module : 'form',
controller : 'passwordFieldController',
templateUrl : 'app/form/templates/passwordField.html'
},
/**
* Enumerated field type. The user is presented a finite list of values
* to choose from.
*
* @see {@link Field.Type.ENUM}
* @type FieldType
*/
'ENUM' : {
module : 'form',
controller : 'selectFieldController',
templateUrl : 'app/form/templates/selectField.html'
},
/**
* Multiline field type. The user may enter multiple lines of text.
*
* @see {@link Field.Type.MULTILINE}
* @type FieldType
*/
'MULTILINE' : {
templateUrl : 'app/form/templates/textAreaField.html'
},
/**
* Field type which allows selection of languages. The languages
* displayed are the set of languages supported by the Guacamole web
* application. Legal values are valid language IDs, as dictated by
* the filenames of Guacamole's available translations.
*
* @see {@link Field.Type.LANGUAGE}
* @type FieldType
*/
'LANGUAGE' : {
module : 'form',
controller : 'languageFieldController',
templateUrl : 'app/form/templates/languageField.html'
},
/**
* Field type which allows selection of time zones.
*
* @see {@link Field.Type.TIMEZONE}
* @type FieldType
*/
'TIMEZONE' : {
module : 'form',
controller : 'timeZoneFieldController',
templateUrl : 'app/form/templates/timeZoneField.html'
},
/**
* Field type which allows selection of individual dates.
*
* @see {@link Field.Type.DATE}
* @type FieldType
*/
'DATE' : {
module : 'form',
controller : 'dateFieldController',
templateUrl : 'app/form/templates/dateField.html'
},
/**
* Field type which allows selection of times of day.
*
* @see {@link Field.Type.TIME}
* @type FieldType
*/
'TIME' : {
module : 'form',
controller : 'timeFieldController',
templateUrl : 'app/form/templates/timeField.html'
},
/**
* Field type which allows selection of color schemes accepted by the
* Guacamole server terminal emulator and protocols which leverage it.
*
* @see {@link Field.Type.TERMINAL_COLOR_SCHEME}
* @type FieldType
*/
'TERMINAL_COLOR_SCHEME' : {
module : 'form',
controller : 'terminalColorSchemeFieldController',
templateUrl : 'app/form/templates/terminalColorSchemeField.html'
}
};
/**
* Registers a new field type under the given name.
*
* @param {String} fieldTypeName
* The name which uniquely identifies the field type being registered.
*
* @param {FieldType} fieldType
* The field type definition to associate with the given name.
*/
this.registerFieldType = function registerFieldType(fieldTypeName, fieldType) {
// Store field type
provider.fieldTypes[fieldTypeName] = fieldType;
};
// Factory method required by provider
this.$get = ['$injector', function formServiceFactory($injector) {
// Required services
var $compile = $injector.get('$compile');
var $q = $injector.get('$q');
var $templateRequest = $injector.get('$templateRequest');
/**
* Map of module name to the injector instance created for that module.
*
* @type {Object.<String, injector>}
*/
var injectors = {};
var service = {};
service.fieldTypes = provider.fieldTypes;
/**
* Given the name of a module, returns an injector instance which
* injects dependencies within that module. A new injector may be
* created and initialized if no such injector has yet been requested.
* If the injector available to formService already includes the
* requested module, that injector will simply be returned.
*
* @param {String} module
* The name of the module to produce an injector for.
*
* @returns {injector}
* An injector instance which injects dependencies for the given
* module.
*/
var getInjector = function getInjector(module) {
// Use the formService's injector if possible
if ($injector.modules[module])
return $injector;
// If the formService's injector does not include the requested
// module, create the necessary injector, reusing that injector for
// future calls
injectors[module] = injectors[module] || angular.injector(['ng', module]);
return injectors[module];
};
/**
* Compiles and links the field associated with the given name to the given
* scope, producing a distinct and independent DOM Element which functions
* as an instance of that field. The scope object provided must include at
* least the following properties:
*
* namespace:
* 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.
*
* model:
* The current String value of the field, if any.
*
* @param {Element} fieldContainer
* The DOM Element whose contents should be replaced with the
* compiled field template.
*
* @param {String} fieldTypeName
* The name of the field type defining the nature of the element to be
* created.
*
* @param {Object} scope
* The scope to which the new element will be linked.
*
* @return {Promise.<Element>}
* A Promise which resolves to the compiled Element. If an error occurs
* while retrieving the field type, this Promise will be rejected.
*/
service.insertFieldElement = function insertFieldElement(fieldContainer,
fieldTypeName, scope) {
// Ensure field type is defined
var fieldType = provider.fieldTypes[fieldTypeName];
if (!fieldType)
return $q.reject();
var templateRequest;
// Use raw HTML template if provided
if (fieldType.template) {
var deferredTemplate = $q.defer();
deferredTemplate.resolve(fieldType.template);
templateRequest = deferredTemplate.promise;
}
// If no raw HTML template is provided, retrieve template from URL
else if (fieldType.templateUrl)
templateRequest = $templateRequest(fieldType.templateUrl);
// Otherwise, use empty template
else {
var emptyTemplate= $q.defer();
emptyTemplate.resolve('');
templateRequest = emptyTemplate.promise;
}
// Defer compilation of template pending successful retrieval
var compiledTemplate = $q.defer();
// Resolve with compiled HTML upon success
templateRequest.then(function templateRetrieved(html) {
// Insert template into DOM
fieldContainer.innerHTML = html;
// Populate scope using defined controller
if (fieldType.module && fieldType.controller) {
var $controller = getInjector(fieldType.module).get('$controller');
$controller(fieldType.controller, {
'$scope' : scope,
'$element' : angular.element(fieldContainer.childNodes)
});
}
// Compile DOM with populated scope
compiledTemplate.resolve($compile(fieldContainer.childNodes)(scope));
})
// Reject on failure
['catch'](function templateError() {
compiledTemplate.reject();
});
// Return promise which resolves to the compiled template
return compiledTemplate.promise;
};
return service;
}];
});