forked from mirrors/gecko-dev
245 lines
6.9 KiB
JavaScript
245 lines
6.9 KiB
JavaScript
/*
|
|
* Copyright 2012, Mozilla Foundation and contributors
|
|
*
|
|
* Licensed 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.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var util = require('../util/util');
|
|
|
|
/**
|
|
* A Field is a way to get input for a single parameter.
|
|
* This class is designed to be inherited from. It's important that all
|
|
* subclasses have a similar constructor signature because they are created
|
|
* via Fields.get(...)
|
|
* @param type The type to use in conversions
|
|
* @param options A set of properties to help fields configure themselves:
|
|
* - document: The document we use in calling createElement
|
|
* - requisition: The requisition that we're attached to
|
|
*/
|
|
function Field(type, options) {
|
|
this.type = type;
|
|
this.document = options.document;
|
|
this.requisition = options.requisition;
|
|
}
|
|
|
|
/**
|
|
* Enable registration of fields using addItems
|
|
*/
|
|
Field.prototype.item = 'field';
|
|
|
|
/**
|
|
* Subclasses should assign their element with the DOM node that gets added
|
|
* to the 'form'. It doesn't have to be an input node, just something that
|
|
* contains it.
|
|
*/
|
|
Field.prototype.element = undefined;
|
|
|
|
/**
|
|
* Called from the outside to indicate that the command line has changed and
|
|
* the field should update itself
|
|
*/
|
|
Field.prototype.update = function() {
|
|
};
|
|
|
|
/**
|
|
* Indicates that this field should drop any resources that it has created
|
|
*/
|
|
Field.prototype.destroy = function() {
|
|
this.messageElement = undefined;
|
|
this.document = undefined;
|
|
this.requisition = undefined;
|
|
};
|
|
|
|
// Note: We could/should probably change Fields from working with Conversions
|
|
// to working with Arguments (Tokens), which makes for less calls to parse()
|
|
|
|
/**
|
|
* Update this field display with the value from this conversion.
|
|
* Subclasses should provide an implementation of this function.
|
|
*/
|
|
Field.prototype.setConversion = function(conversion) {
|
|
throw new Error('Field should not be used directly');
|
|
};
|
|
|
|
/**
|
|
* Extract a conversion from the values in this field.
|
|
* Subclasses should provide an implementation of this function.
|
|
*/
|
|
Field.prototype.getConversion = function() {
|
|
throw new Error('Field should not be used directly');
|
|
};
|
|
|
|
/**
|
|
* Set the element where messages and validation errors will be displayed
|
|
* @see setMessage()
|
|
*/
|
|
Field.prototype.setMessageElement = function(element) {
|
|
this.messageElement = element;
|
|
};
|
|
|
|
/**
|
|
* Display a validation message in the UI
|
|
*/
|
|
Field.prototype.setMessage = function(message) {
|
|
if (this.messageElement) {
|
|
util.setTextContent(this.messageElement, message || '');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Some fields contain information that is more important to the user, for
|
|
* example error messages and completion menus.
|
|
*/
|
|
Field.prototype.isImportant = false;
|
|
|
|
/**
|
|
* 'static/abstract' method to allow implementations of Field to lay a claim
|
|
* to a type. This allows claims of various strength to be weighted up.
|
|
* See the Field.*MATCH values.
|
|
*/
|
|
Field.claim = function(type, context) {
|
|
throw new Error('Field should not be used directly');
|
|
};
|
|
|
|
/**
|
|
* How good a match is a field for a given type
|
|
*/
|
|
Field.MATCH = 3; // Good match
|
|
Field.DEFAULT = 2; // A default match
|
|
Field.BASIC = 1; // OK in an emergency. i.e. assume Strings
|
|
Field.NO_MATCH = 0; // This field can't help with the given type
|
|
|
|
exports.Field = Field;
|
|
|
|
|
|
/**
|
|
* A manager for the registered Fields
|
|
*/
|
|
function Fields() {
|
|
// Internal array of known fields
|
|
this._fieldCtors = [];
|
|
}
|
|
|
|
/**
|
|
* Add a field definition by field constructor
|
|
* @param fieldCtor Constructor function of new Field
|
|
*/
|
|
Fields.prototype.add = function(fieldCtor) {
|
|
if (typeof fieldCtor !== 'function') {
|
|
console.error('fields.add erroring on ', fieldCtor);
|
|
throw new Error('fields.add requires a Field constructor');
|
|
}
|
|
this._fieldCtors.push(fieldCtor);
|
|
};
|
|
|
|
/**
|
|
* Remove a Field definition
|
|
* @param field A previously registered field, specified either with a field
|
|
* name or from the field name
|
|
*/
|
|
Fields.prototype.remove = function(field) {
|
|
if (typeof field !== 'string') {
|
|
this._fieldCtors = this._fieldCtors.filter(function(test) {
|
|
return test !== field;
|
|
});
|
|
}
|
|
else if (field instanceof Field) {
|
|
this.remove(field.name);
|
|
}
|
|
else {
|
|
console.error('fields.remove erroring on ', field);
|
|
throw new Error('fields.remove requires an instance of Field');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Find the best possible matching field from the specification of the type
|
|
* of field required.
|
|
* @param type An instance of Type that we will represent
|
|
* @param options A set of properties that we should attempt to match, and use
|
|
* in the construction of the new field object:
|
|
* - document: The document to use in creating new elements
|
|
* - requisition: The requisition we're monitoring,
|
|
* @return A newly constructed field that best matches the input options
|
|
*/
|
|
Fields.prototype.get = function(type, options) {
|
|
var FieldConstructor;
|
|
var highestClaim = -1;
|
|
this._fieldCtors.forEach(function(fieldCtor) {
|
|
var context = (options.requisition == null) ?
|
|
null : options.requisition.executionContext;
|
|
var claim = fieldCtor.claim(type, context);
|
|
if (claim > highestClaim) {
|
|
highestClaim = claim;
|
|
FieldConstructor = fieldCtor;
|
|
}
|
|
});
|
|
|
|
if (!FieldConstructor) {
|
|
console.error('Unknown field type ', type, ' in ', this._fieldCtors);
|
|
throw new Error('Can\'t find field for ' + type);
|
|
}
|
|
|
|
if (highestClaim < Field.DEFAULT) {
|
|
return new BlankField(type, options);
|
|
}
|
|
|
|
return new FieldConstructor(type, options);
|
|
};
|
|
|
|
/**
|
|
* Get all the registered fields. Most for debugging
|
|
*/
|
|
Fields.prototype.getAll = function() {
|
|
return this._fieldCtors.slice();
|
|
};
|
|
|
|
exports.Fields = Fields;
|
|
|
|
/**
|
|
* For use with delegate types that do not yet have anything to resolve to.
|
|
* BlankFields are not for general use.
|
|
*/
|
|
function BlankField(type, options) {
|
|
Field.call(this, type, options);
|
|
|
|
this.element = util.createElement(this.document, 'div');
|
|
|
|
this.onFieldChange = util.createEvent('BlankField.onFieldChange');
|
|
}
|
|
|
|
BlankField.prototype = Object.create(Field.prototype);
|
|
|
|
BlankField.claim = function(type, context) {
|
|
return type.name === 'blank' ? Field.MATCH : Field.NO_MATCH;
|
|
};
|
|
|
|
BlankField.prototype.destroy = function() {
|
|
this.element = undefined;
|
|
Field.prototype.destroy.call(this);
|
|
};
|
|
|
|
BlankField.prototype.setConversion = function(conversion) {
|
|
this.setMessage(conversion.message);
|
|
};
|
|
|
|
BlankField.prototype.getConversion = function() {
|
|
return this.type.parseString('', this.requisition.executionContext);
|
|
};
|
|
|
|
/**
|
|
* Items for export
|
|
*/
|
|
exports.items = [ BlankField ];
|