fune/devtools/shared/gcli/source/lib/gcli/fields/fields.js

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 ];