blob: 160c161a1296c741c9459005fb3900f051d1bcf9 [file] [log] [blame]
// Copyright 2009 Google Inc. All Rights Reserved.
//
// 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.
/**
* @fileoverview Browser-based UI for viewing error locations.
*/
goog.provide('bidichecker.ErrorGui');
goog.require('bidichecker.Error');
goog.require('goog.dom');
goog.require('goog.events.Event');
goog.require('goog.string');
goog.require('goog.style');
goog.require('goog.ui.Dialog');
goog.require('goog.ui.Dialog.ButtonSet');
/**
* In-browser interactive error viewer.
* @param {Array.<!bidichecker.Error>} errors The errors to display.
* @constructor
*/
bidichecker.ErrorGui = function(errors) {
/**
* The errors to display.
* @type {Array.<!bidichecker.Error>}
* @private
*/
this.errors_ = errors;
/**
* Currently displayed error number (indexed from 0).
* @type {number}
* @private
*/
this.currentError_ = 0;
/**
* Element representing the dialog box.
* @type {Element}
* @private
*/
this.dialogElement_ = null;
/**
* Element representing the error number input field.
* @type {Element}
* @private
*/
this.errorNumField_ = null;
/**
* Element representing the error message display field.
* @type {Element}
* @private
*/
this.errorTextElem_ = null;
};
/**
* Builds and launches the error browser dialog box.
*/
bidichecker.ErrorGui.prototype.launch = function() {
if (this.dialogElement_ || // The UI has already been launched.
this.errors_.length == 0) { // Nothing to display.
return;
}
this.createDialogBox_();
this.highlightCurrentError_();
this.listen_();
};
/**
* Style definitions for the dialog box elements.
* @const
* @type {string}
* @private
*/
bidichecker.ErrorGui.DIALOG_STYLES_ =
'.bidichecker-dialog-bg { position: absolute; top: 0; left: 0; }' +
'.bidichecker-dialog { position: absolute; padding: 5px;' +
' background-color: #fcb; border: 1px solid #000; ' +
'font-family: arial, sans-serif; width: 475px; color: #000; ' +
'outline: none; direction: ltr }' +
'.bidichecker-dialog a, .bidichecker-dialog a:link,' +
'.bidichecker-dialog a:visited { color: #0066cc; cursor: pointer; }' +
'.bidichecker-dialog-title { position: relative; ' +
'background-color: #f9efeb; color: #000000; padding: 10px 15px;' +
' font-size: 16px; font-weight: bold; vertical-align: middle; ' +
'cursor: pointer; cursor: hand }' +
'.bidichecker-dialog-content { padding: 15px; font-size: 90%; ' +
'background-color: #fff }' +
'.bidichecker-dialog-buttons { padding: 0 15px 15px; ' +
'background-color: #fff }' +
'.bidichecker-dialog-error-text { font-weight: bold; font-size: 125%; ' +
'color: #f00; height: 80px; overflow: auto; border: 1px dotted #f00 }';
/**
* HTML content for the dialog box.
* TODO(user): Use a message catalog to support internationalization of the
* dialog box contents.
* @const
* @type {string}
* @private
*/
bidichecker.ErrorGui.DIALOG_CONTENT_ =
// A field for displaying the text of the error message.
'<div id="bidichecker-dialog-error-text" ' +
'class="bidichecker-dialog-error-text"> </div>' +
// An input field for displaying and changing the current error number.
'<p>Error <input name="current" id="bidichecker-dialog-error-num" ' +
'type="text" size="3" value="1" /> of ' +
// A field for displaying the total number of errors.
'<span id="bidichecker-dialog-error-count"></span></p>' +
'<p style="font-size:75%">Drag this box if it conceals a part of the ' +
'page you need.</p>';
/**
* Assembles the error browser dialog box.
* @private
*/
bidichecker.ErrorGui.prototype.createDialogBox_ = function() {
/**
* @type {goog.ui.Dialog}
* @private
*/
this.dialog_ = new goog.ui.Dialog('bidichecker-dialog');
this.dialog_.setTitle('BidiChecker error browser');
this.dialog_.setModal(false); // Underlying page remains accessible.
var buttonset = new goog.ui.Dialog.ButtonSet();
buttonset.set('prev', '< Prev');
buttonset.set('next', 'Next >');
this.dialog_.setButtonSet(buttonset);
this.dialog_.setContent(bidichecker.ErrorGui.DIALOG_CONTENT_);
this.dialog_.setVisible(true);
this.dialogElement_ = this.dialog_.getElement();
goog.style.installStyles(bidichecker.ErrorGui.DIALOG_STYLES_,
this.dialogElement_);
// Prevent page elements from leaking through the dialog box.
this.dialogElement_.style.zIndex = 10000;
var dom = this.dialog_.getDomHelper();
var errorCountField = dom.getElement('bidichecker-dialog-error-count');
goog.dom.setTextContent(errorCountField, this.errors_.length + '');
this.errorNumField_ = dom.getElement('bidichecker-dialog-error-num');
this.errorTextElem_ = dom.getElement('bidichecker-dialog-error-text');
// Make sure the box is centered on the page.
this.dialog_.reposition();
// TODO(user): The dialog box doesn't function correctly on YouTube pages.
// Figure out why and fix it.
};
/**
* Starts listening for user events: button presses and changes to the error
* number input field.
* @private
*/
bidichecker.ErrorGui.prototype.listen_ = function() {
// Listen for button presses.
goog.events.listen(this.dialog_, goog.ui.Dialog.EventType.SELECT,
this.handleButtonEvent_, false, this);
// Listen for changes to the error number input field.
this.errorNumField_.onchange =
goog.bind(this.handleErrorNumFieldChange_, this);
};
/**
* Handles changes to the current error number input field.
* @private
*/
bidichecker.ErrorGui.prototype.handleErrorNumFieldChange_ = function() {
var newError = goog.string.isNumeric(this.errorNumField_.value) ?
goog.string.toNumber(this.errorNumField_.value) - 1 : -1;
if (newError >= 0 && newError < this.errors_.length &&
newError != this.currentError_) {
this.changeCurrentError_(newError);
} else {
// If the input value was invalid, reset the field contents to the current
// error.
this.setErrorNumField_();
}
};
/**
* Handles button presses.
* @param {!goog.events.Event} event A button press event.
* @return {boolean} Whether the dialog box should be dismissed.
* @private
*/
bidichecker.ErrorGui.prototype.handleButtonEvent_ = function(event) {
var newError;
if (event.key == 'next') {
// Advance to next error, wrapping around the end.
newError = this.currentError_ == this.errors_.length - 1 ? 0 :
this.currentError_ + 1;
} else {
// Revert to previous error, wrapping around the start.
newError = this.currentError_ == 0 ? this.errors_.length - 1 :
this.currentError_ - 1;
}
this.changeCurrentError_(newError);
return false;
};
/**
* Displays the current error and scrolls the window to show its position.
* @private
*/
bidichecker.ErrorGui.prototype.highlightCurrentError_ = function() {
var error = this.errors_[this.currentError_];
var highlightableArea = error.getHighlightableArea();
if (highlightableArea) {
var coords = highlightableArea.highlightOnPage();
this.scrollTo_(coords);
}
goog.dom.setTextContent(this.errorTextElem_, error.toString());
};
/**
* Clears highlighting from the current error.
* @private
*/
bidichecker.ErrorGui.prototype.unhighlightCurrentError_ = function() {
var error = this.errors_[this.currentError_];
var highlightableArea = error.getHighlightableArea();
if (highlightableArea) {
highlightableArea.unhighlightOnPage();
}
};
/**
* Changes the currently-displayed error.
* @param {number} newErrorNumber The number of the new error to display.
* @private
*/
bidichecker.ErrorGui.prototype.changeCurrentError_ = function(newErrorNumber) {
this.unhighlightCurrentError_();
this.currentError_ = newErrorNumber;
this.setErrorNumField_();
this.highlightCurrentError_();
};
/**
* Sets the error number field to display the current error number.
* @private
*/
bidichecker.ErrorGui.prototype.setErrorNumField_ = function() {
this.errorNumField_.value = this.currentError_ + 1;
};
/**
* Scrolls the window so a given page position is near the top of the screen.
* Also scrolls the dialog box by the opposite amount to keep it in the same
* place in the viewport.
* TODO(user): Make this do the right thing for frames and other scrollable
* elements.
* @param {goog.math.Coordinate} coords The coordinates to scroll to.
* @private
*/
bidichecker.ErrorGui.prototype.scrollTo_ = function(coords) {
var oldY = window.scrollY;
window.scrollTo(0, coords.y - 100);
this.scrollDialogBoxBy_(window.scrollY - oldY);
};
/**
* Moves the dialog box by a given vertical amount.
* @param {number} scrollY The distance to move it, in pixels.
* @private
*/
bidichecker.ErrorGui.prototype.scrollDialogBoxBy_ = function(scrollY) {
var coords = goog.style.getPosition(this.dialogElement_);
goog.style.setPosition(this.dialogElement_, coords.x, coords.y + scrollY);
};