Bug 1199579 - Eslint cleanup of inplace-editor r=pbro

This commit is contained in:
Gabriel Luong 2015-08-28 21:23:51 -07:00
Родитель 9cf6796e0e
Коммит d16bce4b19
1 изменённых файлов: 210 добавлений и 227 удалений

Просмотреть файл

@ -1,11 +1,12 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */ /* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* globals focusManager, CSSPropertyList, domUtils */
/** /**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Basic use: * Basic use:
* let spanToEdit = document.getElementById("somespan"); * let spanToEdit = document.getElementById("somespan");
* *
@ -48,28 +49,28 @@ Cu.import("resource://gre/modules/devtools/event-emitter.js");
* Changes will be committed when the InlineEditor's input is blurred * Changes will be committed when the InlineEditor's input is blurred
* or dropped when the user presses escape. * or dropped when the user presses escape.
* *
* @param {object} aOptions * @param {object} options
* Options for the editable field, including: * Options for the editable field, including:
* {Element} element: * {Element} element:
* (required) The span to be edited on focus. * (required) The span to be edited on focus.
* {function} canEdit: * {Function} canEdit:
* Will be called before creating the inplace editor. Editor * Will be called before creating the inplace editor. Editor
* won't be created if canEdit returns false. * won't be created if canEdit returns false.
* {function} start: * {Function} start:
* Will be called when the inplace editor is initialized. * Will be called when the inplace editor is initialized.
* {function} change: * {Function} change:
* Will be called when the text input changes. Will be called * Will be called when the text input changes. Will be called
* with the current value of the text input. * with the current value of the text input.
* {function} done: * {Function} done:
* Called when input is committed or blurred. Called with * Called when input is committed or blurred. Called with
* current value, a boolean telling the caller whether to * current value, a boolean telling the caller whether to
* commit the change, and the direction of the next element to be * commit the change, and the direction of the next element to be
* selected. Direction may be one of nsIFocusManager.MOVEFOCUS_FORWARD, * selected. Direction may be one of nsIFocusManager.MOVEFOCUS_FORWARD,
* nsIFocusManager.MOVEFOCUS_BACKWARD, or null (no movement). * nsIFocusManager.MOVEFOCUS_BACKWARD, or null (no movement).
* This function is called before the editor has been torn down. * This function is called before the editor has been torn down.
* {function} destroy: * {Function} destroy:
* Called when the editor is destroyed and has been torn down. * Called when the editor is destroyed and has been torn down.
* {object} advanceChars: * {Object} advanceChars:
* This can be either a string or a function. * This can be either a string or a function.
* If it is a string, then if any characters in it are typed, * If it is a string, then if any characters in it are typed,
* focus will advance to the next element. * focus will advance to the next element.
@ -78,27 +79,26 @@ Cu.import("resource://gre/modules/devtools/event-emitter.js");
* and the insertion point. If the function returns true, * and the insertion point. If the function returns true,
* then the focus advance takes place. If it returns false, * then the focus advance takes place. If it returns false,
* then the character is inserted instead. * then the character is inserted instead.
* {boolean} stopOnReturn: * {Boolean} stopOnReturn:
* If true, the return key will not advance the editor to the next * If true, the return key will not advance the editor to the next
* focusable element. * focusable element.
* {boolean} stopOnTab: * {Boolean} stopOnTab:
* If true, the tab key will not advance the editor to the next * If true, the tab key will not advance the editor to the next
* focusable element. * focusable element.
* {boolean} stopOnShiftTab: * {Boolean} stopOnShiftTab:
* If true, shift tab will not advance the editor to the previous * If true, shift tab will not advance the editor to the previous
* focusable element. * focusable element.
* {string} trigger: The DOM event that should trigger editing, * {String} trigger: The DOM event that should trigger editing,
* defaults to "click" * defaults to "click"
* {boolean} multiline: Should the editor be a multiline textarea? * {Boolean} multiline: Should the editor be a multiline textarea?
* defaults to false * defaults to false
* {boolean} trimOutput: Should the returned string be trimmed? * {Boolean} trimOutput: Should the returned string be trimmed?
* defaults to true * defaults to true
*/ */
function editableField(aOptions) function editableField(options) {
{ return editableItem(options, function(element, event) {
return editableItem(aOptions, function(aElement, aEvent) { if (!options.element.inplaceEditor) {
if (!aOptions.element.inplaceEditor) { new InplaceEditor(options, event);
new InplaceEditor(aOptions, aEvent);
} }
}); });
} }
@ -110,25 +110,24 @@ exports.editableField = editableField;
* clicks and sit in the editing tab order, and call * clicks and sit in the editing tab order, and call
* a callback when it is activated. * a callback when it is activated.
* *
* @param {object} aOptions * @param {Object} options
* The options for this editor, including: * The options for this editor, including:
* {Element} element: The DOM element. * {Element} element: The DOM element.
* {string} trigger: The DOM event that should trigger editing, * {String} trigger: The DOM event that should trigger editing,
* defaults to "click" * defaults to "click"
* @param {function} aCallback * @param {Function} callback
* Called when the editor is activated. * Called when the editor is activated.
* @return {function} function which calls aCallback * @return {Function} function which calls callback
*/ */
function editableItem(aOptions, aCallback) function editableItem(options, callback) {
{ let trigger = options.trigger || "click";
let trigger = aOptions.trigger || "click" let element = options.element;
let element = aOptions.element;
element.addEventListener(trigger, function(evt) { element.addEventListener(trigger, function(evt) {
if (evt.target.nodeName !== "a") { if (evt.target.nodeName !== "a") {
let win = this.ownerDocument.defaultView; let win = this.ownerDocument.defaultView;
let selection = win.getSelection(); let selection = win.getSelection();
if (trigger != "click" || selection.isCollapsed) { if (trigger != "click" || selection.isCollapsed) {
aCallback(element, evt); callback(element, evt);
} }
evt.stopPropagation(); evt.stopPropagation();
} }
@ -139,7 +138,7 @@ function editableItem(aOptions, aCallback)
element.addEventListener("keypress", function(evt) { element.addEventListener("keypress", function(evt) {
if (evt.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN || if (evt.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
evt.charCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) { evt.charCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
aCallback(element); callback(element);
} }
}, true); }, true);
@ -168,8 +167,8 @@ function editableItem(aOptions, aCallback)
element._trigger = trigger; element._trigger = trigger;
return function turnOnEditMode() { return function turnOnEditMode() {
aCallback(element); callback(element);
} };
} }
exports.editableItem = this.editableItem; exports.editableItem = this.editableItem;
@ -181,31 +180,32 @@ exports.editableItem = this.editableItem;
* within this JSM. So we provide a little workaround here. * within this JSM. So we provide a little workaround here.
*/ */
function getInplaceEditorForSpan(aSpan) function getInplaceEditorForSpan(span) {
{ return span.inplaceEditor;
return aSpan.inplaceEditor; }
};
exports.getInplaceEditorForSpan = getInplaceEditorForSpan; exports.getInplaceEditorForSpan = getInplaceEditorForSpan;
function InplaceEditor(aOptions, aEvent) function InplaceEditor(options, event) {
{ this.elt = options.element;
this.elt = aOptions.element;
let doc = this.elt.ownerDocument; let doc = this.elt.ownerDocument;
this.doc = doc; this.doc = doc;
this.elt.inplaceEditor = this; this.elt.inplaceEditor = this;
this.change = aOptions.change; this.change = options.change;
this.done = aOptions.done; this.done = options.done;
this.destroy = aOptions.destroy; this.destroy = options.destroy;
this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent; this.initial = options.initial ? options.initial : this.elt.textContent;
this.multiline = aOptions.multiline || false; this.multiline = options.multiline || false;
this.trimOutput = aOptions.trimOutput === undefined ? true : !!aOptions.trimOutput; this.trimOutput = options.trimOutput === undefined
this.stopOnShiftTab = !!aOptions.stopOnShiftTab; ? true
this.stopOnTab = !!aOptions.stopOnTab; : !!options.trimOutput;
this.stopOnReturn = !!aOptions.stopOnReturn; this.stopOnShiftTab = !!options.stopOnShiftTab;
this.contentType = aOptions.contentType || CONTENT_TYPES.PLAIN_TEXT; this.stopOnTab = !!options.stopOnTab;
this.property = aOptions.property; this.stopOnReturn = !!options.stopOnReturn;
this.popup = aOptions.popup; this.contentType = options.contentType || CONTENT_TYPES.PLAIN_TEXT;
this.property = options.property;
this.popup = options.popup;
this._onBlur = this._onBlur.bind(this); this._onBlur = this._onBlur.bind(this);
this._onKeyPress = this._onKeyPress.bind(this); this._onKeyPress = this._onKeyPress.bind(this);
@ -218,11 +218,11 @@ function InplaceEditor(aOptions, aEvent)
// Pull out character codes for advanceChars, listing the // Pull out character codes for advanceChars, listing the
// characters that should trigger a blur. // characters that should trigger a blur.
if (typeof(aOptions.advanceChars) === "function") { if (typeof options.advanceChars === "function") {
this._advanceChars = aOptions.advanceChars; this._advanceChars = options.advanceChars;
} else { } else {
let advanceCharcodes = {}; let advanceCharcodes = {};
let advanceChars = aOptions.advanceChars || ''; let advanceChars = options.advanceChars || "";
for (let i = 0; i < advanceChars.length; i++) { for (let i = 0; i < advanceChars.length; i++) {
advanceCharcodes[advanceChars.charCodeAt(i)] = true; advanceCharcodes[advanceChars.charCodeAt(i)] = true;
} }
@ -236,7 +236,7 @@ function InplaceEditor(aOptions, aEvent)
this.input.focus(); this.input.focus();
if (typeof(aOptions.selectAll) == "undefined" || aOptions.selectAll) { if (typeof options.selectAll == "undefined" || options.selectAll) {
this.input.select(); this.input.select();
} }
@ -253,7 +253,7 @@ function InplaceEditor(aOptions, aEvent)
this.input.addEventListener("mousedown", this.input.addEventListener("mousedown",
(e) => { e.stopPropagation(); }, false); (e) => { e.stopPropagation(); }, false);
this.validate = aOptions.validate; this.validate = options.validate;
if (this.validate) { if (this.validate) {
this.input.addEventListener("keyup", this._onKeyup, false); this.input.addEventListener("keyup", this._onKeyup, false);
@ -261,8 +261,8 @@ function InplaceEditor(aOptions, aEvent)
this._updateSize(); this._updateSize();
if (aOptions.start) { if (options.start) {
aOptions.start(this, aEvent); options.start(this, event);
} }
EventEmitter.decorate(this); EventEmitter.decorate(this);
@ -279,8 +279,7 @@ InplaceEditor.prototype = {
return val; return val;
}, },
_createInput: function InplaceEditor_createEditor() _createInput: function() {
{
this.input = this.input =
this.doc.createElementNS(HTML_NS, this.multiline ? "textarea" : "input"); this.doc.createElementNS(HTML_NS, this.multiline ? "textarea" : "input");
this.input.inplaceEditor = this; this.input.inplaceEditor = this;
@ -293,8 +292,7 @@ InplaceEditor.prototype = {
/** /**
* Get rid of the editor. * Get rid of the editor.
*/ */
_clear: function InplaceEditor_clear() _clear: function() {
{
if (!this.input) { if (!this.input) {
// Already cleared. // Already cleared.
return; return;
@ -327,8 +325,7 @@ InplaceEditor.prototype = {
* Keeps the editor close to the size of its input string. This is pretty * Keeps the editor close to the size of its input string. This is pretty
* crappy, suggestions for improvement welcome. * crappy, suggestions for improvement welcome.
*/ */
_autosize: function InplaceEditor_autosize() _autosize: function() {
{
// Create a hidden, absolutely-positioned span to measure the text // Create a hidden, absolutely-positioned span to measure the text
// in the input. Boo. // in the input. Boo.
@ -352,8 +349,7 @@ InplaceEditor.prototype = {
/** /**
* Clean up the mess created by _autosize(). * Clean up the mess created by _autosize().
*/ */
_stopAutosize: function InplaceEditor_stopAutosize() _stopAutosize: function() {
{
if (!this._measurement) { if (!this._measurement) {
return; return;
} }
@ -364,12 +360,11 @@ InplaceEditor.prototype = {
/** /**
* Size the editor to fit its current contents. * Size the editor to fit its current contents.
*/ */
_updateSize: function InplaceEditor_updateSize() _updateSize: function() {
{
// Replace spaces with non-breaking spaces. Otherwise setting // Replace spaces with non-breaking spaces. Otherwise setting
// the span's textContent will collapse spaces and the measurement // the span's textContent will collapse spaces and the measurement
// will be wrong. // will be wrong.
this._measurement.textContent = this.input.value.replace(/ /g, '\u00a0'); this._measurement.textContent = this.input.value.replace(/ /g, "\u00a0");
// We add a bit of padding to the end. Should be enough to fit // We add a bit of padding to the end. Should be enough to fit
// any letter that could be typed, otherwise we'll scroll before // any letter that could be typed, otherwise we'll scroll before
@ -391,8 +386,7 @@ InplaceEditor.prototype = {
* Get the width of a single character in the input to properly position the * Get the width of a single character in the input to properly position the
* autocompletion popup. * autocompletion popup.
*/ */
_getInputCharWidth: function InplaceEditor_getInputCharWidth() _getInputCharWidth: function() {
{
// Just make the text content to be 'x' to get the width of any character in // Just make the text content to be 'x' to get the width of any character in
// a monospace font. // a monospace font.
this._measurement.textContent = "x"; this._measurement.textContent = "x";
@ -402,12 +396,11 @@ InplaceEditor.prototype = {
/** /**
* Increment property values in rule view. * Increment property values in rule view.
* *
* @param {number} increment * @param {Number} increment
* The amount to increase/decrease the property value. * The amount to increase/decrease the property value.
* @return {bool} true if value has been incremented. * @return {Boolean} true if value has been incremented.
*/ */
_incrementValue: function InplaceEditor_incrementValue(increment) _incrementValue: function(increment) {
{
let value = this.input.value; let value = this.input.value;
let selectionStart = this.input.selectionStart; let selectionStart = this.input.selectionStart;
let selectionEnd = this.input.selectionEnd; let selectionEnd = this.input.selectionEnd;
@ -434,19 +427,17 @@ InplaceEditor.prototype = {
/** /**
* Increment the property value based on the property type. * Increment the property value based on the property type.
* *
* @param {string} value * @param {String} value
* Property value. * Property value.
* @param {number} increment * @param {Number} increment
* Amount to increase/decrease the property value. * Amount to increase/decrease the property value.
* @param {number} selStart * @param {Number} selStart
* Starting index of the value. * Starting index of the value.
* @param {number} selEnd * @param {Number} selEnd
* Ending index of the value. * Ending index of the value.
* @return {object} object with properties 'value', 'start', and 'end'. * @return {Object} object with properties 'value', 'start', and 'end'.
*/ */
_incrementCSSValue: function InplaceEditor_incrementCSSValue(value, increment, _incrementCSSValue: function(value, increment, selStart, selEnd) {
selStart, selEnd)
{
let range = this._parseCSSValue(value, selStart); let range = this._parseCSSValue(value, selStart);
let type = (range && range.type) || ""; let type = (range && range.type) || "";
let rawValue = (range ? value.substring(range.start, range.end) : ""); let rawValue = (range ? value.substring(range.start, range.end) : "");
@ -489,11 +480,12 @@ InplaceEditor.prototype = {
} }
} }
} }
return this._incrementGenericValue(value, increment, selStart, selEnd, info); return this._incrementGenericValue(value, increment, selStart, selEnd,
info);
} }
if (incrementedValue === null) { if (incrementedValue === null) {
return; return null;
} }
let preRawValue = value.substr(0, range.start); let preRawValue = value.substr(0, range.start);
@ -509,14 +501,14 @@ InplaceEditor.prototype = {
/** /**
* Parses the property value and type. * Parses the property value and type.
* *
* @param {string} value * @param {String} value
* Property value. * Property value.
* @param {number} offset * @param {Number} offset
* Starting index of value. * Starting index of value.
* @return {object} object with properties 'value', 'start', 'end', and 'type'. * @return {Object} object with properties 'value', 'start', 'end', and
* 'type'.
*/ */
_parseCSSValue: function InplaceEditor_parseCSSValue(value, offset) _parseCSSValue: function(value, offset) {
{
const reSplitCSS = /(url\("?[^"\)]+"?\)?)|(rgba?\([^)]*\)?)|(hsla?\([^)]*\)?)|(#[\dA-Fa-f]+)|(-?\d*\.?\d+(%|[a-z]{1,4})?)|"([^"]*)"?|'([^']*)'?|([^,\s\/!\(\)]+)|(!(.*)?)/; const reSplitCSS = /(url\("?[^"\)]+"?\)?)|(rgba?\([^)]*\)?)|(hsla?\([^)]*\)?)|(#[\dA-Fa-f]+)|(-?\d*\.?\d+(%|[a-z]{1,4})?)|"([^"]*)"?|'([^']*)'?|([^,\s\/!\(\)]+)|(!(.*)?)/;
let start = 0; let start = 0;
let m; let m;
@ -530,7 +522,7 @@ InplaceEditor.prototype = {
} }
if (!m) { if (!m) {
return; return null;
} }
let type; let type;
@ -558,22 +550,19 @@ InplaceEditor.prototype = {
* Increment the property value for types other than * Increment the property value for types other than
* number or hex, such as rgb, hsl, and file names. * number or hex, such as rgb, hsl, and file names.
* *
* @param {string} value * @param {String} value
* Property value. * Property value.
* @param {number} increment * @param {Number} increment
* Amount to increment/decrement. * Amount to increment/decrement.
* @param {number} offset * @param {Number} offset
* Starting index of the property value. * Starting index of the property value.
* @param {number} offsetEnd * @param {Number} offsetEnd
* Ending index of the property value. * Ending index of the property value.
* @param {object} info * @param {Object} info
* Object with details about the property value. * Object with details about the property value.
* @return {object} object with properties 'value', 'start', and 'end'. * @return {Object} object with properties 'value', 'start', and 'end'.
*/ */
_incrementGenericValue: _incrementGenericValue: function(value, increment, offset, offsetEnd, info) {
function InplaceEditor_incrementGenericValue(value, increment, offset,
offsetEnd, info)
{
// Try to find a number around the cursor to increment. // Try to find a number around the cursor to increment.
let start, end; let start, end;
// Check if we are incrementing in a non-number context (such as a URL) // Check if we are incrementing in a non-number context (such as a URL)
@ -585,8 +574,8 @@ InplaceEditor.prototype = {
start = offset; start = offset;
end = offsetEnd; end = offsetEnd;
} else { } else {
// Parse periods as belonging to the number only if we are in a known number // Parse periods as belonging to the number only if we are in a known
// context. (This makes incrementing the 1 in 'image1.gif' work.) // number context. (This makes incrementing the 1 in 'image1.gif' work.)
let pattern = "[" + (info ? "0-9." : "0-9") + "]*"; let pattern = "[" + (info ? "0-9." : "0-9") + "]*";
let before = new RegExp(pattern + "$").exec(value.substr(0, offset))[0].length; let before = new RegExp(pattern + "$").exec(value.substr(0, offset))[0].length;
let after = new RegExp("^" + pattern).exec(value.substr(offset))[0].length; let after = new RegExp("^" + pattern).exec(value.substr(offset))[0].length;
@ -602,8 +591,7 @@ InplaceEditor.prototype = {
} }
} }
if (start !== end) if (start !== end) {
{
// Include percentages as part of the incremented number (they are // Include percentages as part of the incremented number (they are
// common enough). // common enough).
if (value.charAt(end) === "%") { if (value.charAt(end) === "%") {
@ -629,17 +617,15 @@ InplaceEditor.prototype = {
/** /**
* Increment the property value for numbers. * Increment the property value for numbers.
* *
* @param {string} rawValue * @param {String} rawValue
* Raw value to increment. * Raw value to increment.
* @param {number} increment * @param {Number} increment
* Amount to increase/decrease the raw value. * Amount to increase/decrease the raw value.
* @param {object} info * @param {Object} info
* Object with info about the property value. * Object with info about the property value.
* @return {string} the incremented value. * @return {String} the incremented value.
*/ */
_incrementRawValue: _incrementRawValue: function(rawValue, increment, info) {
function InplaceEditor_incrementRawValue(rawValue, increment, info)
{
let num = parseFloat(rawValue); let num = parseFloat(rawValue);
if (isNaN(num)) { if (isNaN(num)) {
@ -667,25 +653,23 @@ InplaceEditor.prototype = {
/** /**
* Increment the property value for hex. * Increment the property value for hex.
* *
* @param {string} value * @param {String} value
* Property value. * Property value.
* @param {number} increment * @param {Number} increment
* Amount to increase/decrease the property value. * Amount to increase/decrease the property value.
* @param {number} offset * @param {Number} offset
* Starting index of the property value. * Starting index of the property value.
* @param {number} offsetEnd * @param {Number} offsetEnd
* Ending index of the property value. * Ending index of the property value.
* @return {object} object with properties 'value' and 'selection'. * @return {Object} object with properties 'value' and 'selection'.
*/ */
_incHexColor: _incHexColor: function(rawValue, increment, offset, offsetEnd) {
function InplaceEditor_incHexColor(rawValue, increment, offset, offsetEnd)
{
// Return early if no part of the rawValue is selected. // Return early if no part of the rawValue is selected.
if (offsetEnd > rawValue.length && offset >= rawValue.length) { if (offsetEnd > rawValue.length && offset >= rawValue.length) {
return; return null;
} }
if (offset < 1 && offsetEnd <= 1) { if (offset < 1 && offsetEnd <= 1) {
return; return null;
} }
// Ignore the leading #. // Ignore the leading #.
rawValue = rawValue.substr(1); rawValue = rawValue.substr(1);
@ -707,7 +691,7 @@ InplaceEditor.prototype = {
} }
if (rawValue.length !== 6) { if (rawValue.length !== 6) {
return; return null;
} }
// If no selection, increment an adjacent color, preferably one to the left. // If no selection, increment an adjacent color, preferably one to the left.
@ -724,7 +708,7 @@ InplaceEditor.prototype = {
offsetEnd += offsetEnd % 2; offsetEnd += offsetEnd % 2;
// Remap the increments from [0.1, 1, 10] to [1, 1, 16]. // Remap the increments from [0.1, 1, 10] to [1, 1, 16].
if (-1 < increment && increment < 1) { if (increment > -1 && increment < 1) {
increment = (increment < 0 ? -1 : 1); increment = (increment < 0 ? -1 : 1);
} }
if (Math.abs(increment) === 10) { if (Math.abs(increment) === 10) {
@ -739,7 +723,7 @@ InplaceEditor.prototype = {
let value = parseInt(mid, 16); let value = parseInt(mid, 16);
if (isNaN(value)) { if (isNaN(value)) {
return; return null;
} }
mid = Math.min(Math.max(value + increment, 0), 255).toString(16); mid = Math.min(Math.max(value + increment, 0), 255).toString(16);
@ -763,43 +747,46 @@ InplaceEditor.prototype = {
/** /**
* Cycle through the autocompletion suggestions in the popup. * Cycle through the autocompletion suggestions in the popup.
* *
* @param {boolean} aReverse * @param {Boolean} reverse
* true to select previous item from the popup. * true to select previous item from the popup.
* @param {boolean} aNoSelect * @param {Boolean} noSelect
* true to not select the text after selecting the newly selectedItem * true to not select the text after selecting the newly selectedItem
* from the popup. * from the popup.
*/ */
_cycleCSSSuggestion: _cycleCSSSuggestion: function(reverse, noSelect) {
function InplaceEditor_cycleCSSSuggestion(aReverse, aNoSelect)
{
// selectedItem can be null when nothing is selected in an empty editor. // selectedItem can be null when nothing is selected in an empty editor.
let {label, preLabel} = this.popup.selectedItem || {label: "", preLabel: ""}; let {label, preLabel} = this.popup.selectedItem ||
if (aReverse) { {label: "", preLabel: ""};
if (reverse) {
this.popup.selectPreviousItem(); this.popup.selectPreviousItem();
} else { } else {
this.popup.selectNextItem(); this.popup.selectNextItem();
} }
this._selectedIndex = this.popup.selectedIndex; this._selectedIndex = this.popup.selectedIndex;
let input = this.input; let input = this.input;
let pre = ""; let pre = "";
if (input.selectionStart < input.selectionEnd) { if (input.selectionStart < input.selectionEnd) {
pre = input.value.slice(0, input.selectionStart); pre = input.value.slice(0, input.selectionStart);
} } else {
else {
pre = input.value.slice(0, input.selectionStart - label.length + pre = input.value.slice(0, input.selectionStart - label.length +
preLabel.length); preLabel.length);
} }
let post = input.value.slice(input.selectionEnd, input.value.length); let post = input.value.slice(input.selectionEnd, input.value.length);
let item = this.popup.selectedItem; let item = this.popup.selectedItem;
let toComplete = item.label.slice(item.preLabel.length); let toComplete = item.label.slice(item.preLabel.length);
input.value = pre + toComplete + post; input.value = pre + toComplete + post;
if (!aNoSelect) {
if (!noSelect) {
input.setSelectionRange(pre.length, pre.length + toComplete.length); input.setSelectionRange(pre.length, pre.length + toComplete.length);
} } else {
else {
input.setSelectionRange(pre.length + toComplete.length, input.setSelectionRange(pre.length + toComplete.length,
pre.length + toComplete.length); pre.length + toComplete.length);
} }
this._updateSize(); this._updateSize();
// This emit is mainly for the purpose of making the test flow simpler. // This emit is mainly for the purpose of making the test flow simpler.
this.emit("after-suggest"); this.emit("after-suggest");
@ -808,10 +795,9 @@ InplaceEditor.prototype = {
/** /**
* Call the client's done handler and clear out. * Call the client's done handler and clear out.
*/ */
_apply: function InplaceEditor_apply(aEvent, direction) _apply: function(event, direction) {
{
if (this._applied) { if (this._applied) {
return; return null;
} }
this._applied = true; this._applied = true;
@ -827,26 +813,28 @@ InplaceEditor.prototype = {
/** /**
* Handle loss of focus by calling done if it hasn't been called yet. * Handle loss of focus by calling done if it hasn't been called yet.
*/ */
_onBlur: function InplaceEditor_onBlur(aEvent, aDoNotClear) _onBlur: function(event, doNotClear) {
{ if (event && this.popup && this.popup.isOpen &&
if (aEvent && this.popup && this.popup.isOpen &&
this.popup.selectedIndex >= 0) { this.popup.selectedIndex >= 0) {
let label, preLabel; let label, preLabel;
if (this._selectedIndex === undefined) {
({label, preLabel} = this.popup.getItemAtIndex(this.popup.selectedIndex)); if (!this._selectedIndex) {
} ({label, preLabel} =
else { this.popup.getItemAtIndex(this.popup.selectedIndex));
} else {
({label, preLabel} = this.popup.getItemAtIndex(this._selectedIndex)); ({label, preLabel} = this.popup.getItemAtIndex(this._selectedIndex));
} }
let input = this.input; let input = this.input;
let pre = ""; let pre = "";
if (input.selectionStart < input.selectionEnd) { if (input.selectionStart < input.selectionEnd) {
pre = input.value.slice(0, input.selectionStart); pre = input.value.slice(0, input.selectionStart);
} } else {
else {
pre = input.value.slice(0, input.selectionStart - label.length + pre = input.value.slice(0, input.selectionStart - label.length +
preLabel.length); preLabel.length);
} }
let post = input.value.slice(input.selectionEnd, input.value.length); let post = input.value.slice(input.selectionEnd, input.value.length);
let item = this.popup.selectedItem; let item = this.popup.selectedItem;
this._selectedIndex = this.popup.selectedIndex; this._selectedIndex = this.popup.selectedIndex;
@ -873,8 +861,9 @@ InplaceEditor.prototype = {
} }
return; return;
} }
this._apply(); this._apply();
if (!aDoNotClear) { if (!doNotClear) {
this._clear(); this._clear();
} }
}, },
@ -882,8 +871,7 @@ InplaceEditor.prototype = {
/** /**
* Handle the input field's keypress event. * Handle the input field's keypress event.
*/ */
_onKeyPress: function InplaceEditor_onKeyPress(aEvent) _onKeyPress: function(event) {
{
let prevent = false; let prevent = false;
const largeIncrement = 100; const largeIncrement = 100;
@ -892,35 +880,35 @@ InplaceEditor.prototype = {
let increment = 0; let increment = 0;
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP ||
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) { event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
increment = 1; increment = 1;
} else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN } else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN ||
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) { event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
increment = -1; increment = -1;
} }
if (aEvent.shiftKey && !aEvent.altKey) { if (event.shiftKey && !event.altKey) {
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP ||
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) { event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
increment *= largeIncrement; increment *= largeIncrement;
} else { } else {
increment *= mediumIncrement; increment *= mediumIncrement;
} }
} else if (aEvent.altKey && !aEvent.shiftKey) { } else if (event.altKey && !event.shiftKey) {
increment *= smallIncrement; increment *= smallIncrement;
} }
// Use default cursor movement rather than providing auto-suggestions. // Use default cursor movement rather than providing auto-suggestions.
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME ||
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END ||
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP ||
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) { event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
this._preventSuggestions = true; this._preventSuggestions = true;
} }
let cycling = false; let cycling = false;
if (increment && this._incrementValue(increment) ) { if (increment && this._incrementValue(increment)) {
this._updateSize(); this._updateSize();
prevent = true; prevent = true;
cycling = true; cycling = true;
@ -931,30 +919,30 @@ InplaceEditor.prototype = {
this._doValidation(); this._doValidation();
} }
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE || if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE ||
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DELETE || event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DELETE ||
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_LEFT || event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_LEFT ||
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) { event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) {
if (this.popup && this.popup.isOpen) { if (this.popup && this.popup.isOpen) {
this.popup.hidePopup(); this.popup.hidePopup();
} }
} else if (!cycling && !aEvent.metaKey && !aEvent.altKey && !aEvent.ctrlKey) { } else if (!cycling && !event.metaKey && !event.altKey && !event.ctrlKey) {
this._maybeSuggestCompletion(); this._maybeSuggestCompletion();
} }
if (this.multiline && if (this.multiline &&
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN && event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN &&
aEvent.shiftKey) { event.shiftKey) {
prevent = false; prevent = false;
} else if (this._advanceChars(aEvent.charCode, this.input.value, } else if (this._advanceChars(event.charCode, this.input.value,
this.input.selectionStart) this.input.selectionStart)
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN || event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN
|| aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB) { || event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB) {
prevent = true; prevent = true;
let direction = FOCUS_FORWARD; let direction = FOCUS_FORWARD;
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB && if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
aEvent.shiftKey) { event.shiftKey) {
if (this.stopOnShiftTab) { if (this.stopOnShiftTab) {
direction = null; direction = null;
} else { } else {
@ -962,9 +950,9 @@ InplaceEditor.prototype = {
} }
} }
if ((this.stopOnReturn && if ((this.stopOnReturn &&
aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) || event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) ||
(this.stopOnTab && aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB && (this.stopOnTab && event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
!aEvent.shiftKey)) { !event.shiftKey)) {
direction = null; direction = null;
} }
@ -979,22 +967,21 @@ InplaceEditor.prototype = {
let input = this.input; let input = this.input;
if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB && if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
this.contentType == CONTENT_TYPES.CSS_MIXED) { this.contentType == CONTENT_TYPES.CSS_MIXED) {
if (this.popup && input.selectionStart < input.selectionEnd) { if (this.popup && input.selectionStart < input.selectionEnd) {
aEvent.preventDefault(); event.preventDefault();
input.setSelectionRange(input.selectionEnd, input.selectionEnd); input.setSelectionRange(input.selectionEnd, input.selectionEnd);
this.emit("after-suggest"); this.emit("after-suggest");
return; return;
} } else if (this.popup && this.popup.isOpen) {
else if (this.popup && this.popup.isOpen) { event.preventDefault();
aEvent.preventDefault(); this._cycleCSSSuggestion(event.shiftKey, true);
this._cycleCSSSuggestion(aEvent.shiftKey, true);
return; return;
} }
} }
this._apply(aEvent, direction); this._apply(event, direction);
// Close the popup if open // Close the popup if open
if (this.popup && this.popup.isOpen) { if (this.popup && this.popup.isOpen) {
@ -1009,14 +996,14 @@ InplaceEditor.prototype = {
// If the next node to be focused has been tagged as an editable // If the next node to be focused has been tagged as an editable
// node, trigger editing using the configured event // node, trigger editing using the configured event
if (next && next.ownerDocument === this.doc && next._editable) { if (next && next.ownerDocument === this.doc && next._editable) {
let e = this.doc.createEvent('Event'); let e = this.doc.createEvent("Event");
e.initEvent(next._trigger, true, true); e.initEvent(next._trigger, true, true);
next.dispatchEvent(e); next.dispatchEvent(e);
} }
} }
this._clear(); this._clear();
} else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) { } else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
// Cancel and blur ourselves. // Cancel and blur ourselves.
// Now we don't want to suggest anything as we are moving out. // Now we don't want to suggest anything as we are moving out.
this._preventSuggestions = true; this._preventSuggestions = true;
@ -1028,8 +1015,8 @@ InplaceEditor.prototype = {
this.cancelled = true; this.cancelled = true;
this._apply(); this._apply();
this._clear(); this._clear();
aEvent.stopPropagation(); event.stopPropagation();
} else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) { } else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
// No need for leading spaces here. This is particularly // No need for leading spaces here. This is particularly
// noticable when adding a property: it's very natural to type // noticable when adding a property: it's very natural to type
// <name>: (which advances to the next property) then spacebar. // <name>: (which advances to the next property) then spacebar.
@ -1037,22 +1024,21 @@ InplaceEditor.prototype = {
} }
if (prevent) { if (prevent) {
aEvent.preventDefault(); event.preventDefault();
} }
}, },
/** /**
* Handle the input field's keyup event. * Handle the input field's keyup event.
*/ */
_onKeyup: function(aEvent) { _onKeyup: function() {
this._applied = false; this._applied = false;
}, },
/** /**
* Handle changes to the input text. * Handle changes to the input text.
*/ */
_onInput: function InplaceEditor_onInput(aEvent) _onInput: function() {
{
// Validate the entered value. // Validate the entered value.
this._doValidation(); this._doValidation();
@ -1070,8 +1056,7 @@ InplaceEditor.prototype = {
/** /**
* Fire validation callback with current input * Fire validation callback with current input
*/ */
_doValidation: function() _doValidation: function() {
{
if (this.validate && this.input) { if (this.validate && this.input) {
this.validate(this.input.value); this.validate(this.input.value);
} }
@ -1080,14 +1065,15 @@ InplaceEditor.prototype = {
/** /**
* Handles displaying suggestions based on the current input. * Handles displaying suggestions based on the current input.
* *
* @param {boolean} aNoAutoInsert * @param {Boolean} noAutoInsert
* true if you don't want to automatically insert the first suggestion * true if you don't want to automatically insert the first suggestion
*/ */
_maybeSuggestCompletion: function(aNoAutoInsert) { _maybeSuggestCompletion: function(noAutoInsert) {
// Input can be null in cases when you intantaneously switch out of it. // Input can be null in cases when you intantaneously switch out of it.
if (!this.input) { if (!this.input) {
return; return;
} }
let preTimeoutQuery = this.input.value; let preTimeoutQuery = this.input.value;
// Since we are calling this method from a keypress event handler, the // Since we are calling this method from a keypress event handler, the
// |input.value| does not include currently typed character. Thus we perform // |input.value| does not include currently typed character. Thus we perform
@ -1134,8 +1120,8 @@ InplaceEditor.prototype = {
startCheckQuery = ""; startCheckQuery = "";
} }
list = list = ["!important",
["!important", ...domUtils.getCSSValuesForProperty(this.property.name)]; ...domUtils.getCSSValuesForProperty(this.property.name)];
if (query == "") { if (query == "") {
// Do not suggest '!important' without any manually typed character. // Do not suggest '!important' without any manually typed character.
@ -1146,11 +1132,12 @@ InplaceEditor.prototype = {
// Detecting if cursor is at property or value; // Detecting if cursor is at property or value;
let match = query.match(/([:;"'=]?)\s*([^"';:=]+)?$/); let match = query.match(/([:;"'=]?)\s*([^"';:=]+)?$/);
if (match && match.length >= 2) { if (match && match.length >= 2) {
if (match[1] == ":") { // We are in CSS value completion // We are in CSS value completion
if (match[1] == ":") {
let propertyName = let propertyName =
query.match(/[;"'=]\s*([^"';:= ]+)\s*:\s*[^"';:=]*$/)[1]; query.match(/[;"'=]\s*([^"';:= ]+)\s*:\s*[^"';:=]*$/)[1];
list = list = ["!important;",
["!important;", ...domUtils.getCSSValuesForProperty(propertyName)]; ...domUtils.getCSSValuesForProperty(propertyName)];
let matchLastQuery = /([^\s,.\/]+$)/.exec(match[2] || ""); let matchLastQuery = /([^\s,.\/]+$)/.exec(match[2] || "");
if (matchLastQuery) { if (matchLastQuery) {
startCheckQuery = matchLastQuery[0]; startCheckQuery = matchLastQuery[0];
@ -1161,7 +1148,8 @@ InplaceEditor.prototype = {
// Don't suggest '!important' without any manually typed character // Don't suggest '!important' without any manually typed character
list.splice(0, 1); list.splice(0, 1);
} }
} else if (match[1]) { // We are in CSS property name completion } else if (match[1]) {
// We are in CSS property name completion
list = CSSPropertyList; list = CSSPropertyList;
startCheckQuery = match[2]; startCheckQuery = match[2];
} }
@ -1172,7 +1160,7 @@ InplaceEditor.prototype = {
} }
} }
} }
if (!aNoAutoInsert) { if (!noAutoInsert) {
list.some(item => { list.some(item => {
if (startCheckQuery != null && item.startsWith(startCheckQuery)) { if (startCheckQuery != null && item.startsWith(startCheckQuery)) {
input.value = query + item.slice(startCheckQuery.length) + input.value = query + item.slice(startCheckQuery.length) +
@ -1199,13 +1187,11 @@ InplaceEditor.prototype = {
preLabel: startCheckQuery, preLabel: startCheckQuery,
label: list[i] label: list[i]
}); });
} } else if (count > 0) {
else if (count > 0) {
// Since count was incremented, we had already crossed the entries // Since count was incremented, we had already crossed the entries
// which would have started with query, assuming that list is sorted. // which would have started with query, assuming that list is sorted.
break; break;
} } else if (startCheckQuery != null && list[i][0] > startCheckQuery[0]) {
else if (startCheckQuery != null && list[i][0] > startCheckQuery[0]) {
// We have crossed all possible matches alphabetically. // We have crossed all possible matches alphabetically.
break; break;
} }
@ -1217,7 +1203,7 @@ InplaceEditor.prototype = {
this.inputCharWidth; this.inputCharWidth;
this.popup.setItems(finalList); this.popup.setItems(finalList);
this.popup.openPopup(this.input, x); this.popup.openPopup(this.input, x);
if (aNoAutoInsert) { if (noAutoInsert) {
this.popup.selectedIndex = -1; this.popup.selectedIndex = -1;
} }
} else { } else {
@ -1233,25 +1219,22 @@ InplaceEditor.prototype = {
/** /**
* Copy text-related styles from one element to another. * Copy text-related styles from one element to another.
*/ */
function copyTextStyles(aFrom, aTo) function copyTextStyles(from, to) {
{ let win = from.ownerDocument.defaultView;
let win = aFrom.ownerDocument.defaultView; let style = win.getComputedStyle(from);
let style = win.getComputedStyle(aFrom); to.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText; to.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText; to.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText; to.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
} }
/** /**
* Trigger a focus change similar to pressing tab/shift-tab. * Trigger a focus change similar to pressing tab/shift-tab.
*/ */
function moveFocus(aWin, aDirection) function moveFocus(win, direction) {
{ return focusManager.moveFocus(win, null, direction, 0);
return focusManager.moveFocus(aWin, null, aDirection, 0);
} }
XPCOMUtils.defineLazyGetter(this, "focusManager", function() { XPCOMUtils.defineLazyGetter(this, "focusManager", function() {
return Services.focus; return Services.focus;
}); });