blob: 9101b9cd7509a6de1c5838f43de70334b4ce991a [file] [log] [blame]
/*
* Copyright (C) 2007 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
WebInspector.DatabasePanel = function(database, views)
{
var allViews = [{ title: WebInspector.UIString("Query"), name: "query" }, { title: WebInspector.UIString("Browse"), name: "browse" }];
if (views)
allViews = allViews.concat(views);
WebInspector.ResourcePanel.call(this, database, allViews);
this.currentView = this.views.browse;
this.queryPromptElement = document.createElement("textarea");
this.queryPromptElement.className = "database-prompt";
this.element.appendChild(this.queryPromptElement);
this.queryPromptElement.addEventListener("keydown", this.queryInputKeyDown.bind(this), false);
this.queryPromptHistory = [];
this.queryPromptHistoryOffset = 0;
var queryView = this.views.query;
queryView.commandListElement = document.createElement("ol");
queryView.commandListElement.className = "database-command-list";
queryView.contentElement.appendChild(queryView.commandListElement);
var panel = this;
queryView.show = function()
{
panel.queryPromptElement.focus();
this.commandListElement.scrollTop = this.previousScrollTop;
};
queryView.hide = function()
{
this.previousScrollTop = this.commandListElement.scrollTop;
};
var browseView = this.views.browse;
browseView.reloadTableElement = document.createElement("button");
browseView.reloadTableElement.appendChild(document.createElement("img"));
browseView.reloadTableElement.className = "database-table-reload";
browseView.reloadTableElement.title = WebInspector.UIString("Reload");
browseView.reloadTableElement.addEventListener("click", this.reloadClicked.bind(this), false);
browseView.show = function()
{
panel.updateTableList();
panel.queryPromptElement.focus();
this.tableSelectElement.removeStyleClass("hidden");
if (!this.tableSelectElement.parentNode)
document.getElementById("toolbarButtons").appendChild(this.tableSelectElement);
this.reloadTableElement.removeStyleClass("hidden");
if (!this.reloadTableElement.parentNode)
document.getElementById("toolbarButtons").appendChild(this.reloadTableElement);
this.contentElement.scrollTop = this.previousScrollTop;
};
browseView.hide = function()
{
this.tableSelectElement.addStyleClass("hidden");
this.reloadTableElement.addStyleClass("hidden");
this.previousScrollTop = this.contentElement.scrollTop;
};
}
// FIXME: The function and local variables are a workaround for http://bugs.webkit.org/show_bug.cgi?id=15574.
WebInspector.DatabasePanel.prototype = (function() {
var document = window.document;
var Math = window.Math;
return {
show: function()
{
WebInspector.ResourcePanel.prototype.show.call(this);
this.queryPromptElement.focus();
},
get currentTable()
{
return this._currentTable;
},
set currentTable(x)
{
if (this._currentTable === x)
return;
this._currentTable = x;
if (x) {
var browseView = this.views.browse;
if (browseView.tableSelectElement) {
var length = browseView.tableSelectElement.options.length;
for (var i = 0; i < length; ++i) {
var option = browseView.tableSelectElement.options[i];
if (option.value === x) {
browseView.tableSelectElement.selectedIndex = i;
break;
}
}
}
this.updateTableBrowser();
}
},
reloadClicked: function()
{
this.updateTableList();
this.updateTableBrowser();
},
updateTableList: function()
{
var browseView = this.views.browse;
if (!browseView.tableSelectElement) {
browseView.tableSelectElement = document.createElement("select");
browseView.tableSelectElement.className = "database-table-select hidden";
var panel = this;
var changeTableFunction = function()
{
var index = browseView.tableSelectElement.selectedIndex;
if (index != -1)
panel.currentTable = browseView.tableSelectElement.options[index].value;
else
panel.currentTable = null;
};
browseView.tableSelectElement.addEventListener("change", changeTableFunction, false);
}
browseView.tableSelectElement.removeChildren();
var selectedTableName = this.currentTable;
var tableNames = InspectorController.databaseTableNames(this.resource.database).sort();
var length = tableNames.length;
for (var i = 0; i < length; ++i) {
var option = document.createElement("option");
option.value = tableNames[i];
option.text = tableNames[i];
browseView.tableSelectElement.appendChild(option);
if (tableNames[i] === selectedTableName)
browseView.tableSelectElement.selectedIndex = i;
}
if (!selectedTableName && length)
this.currentTable = tableNames[0];
},
updateTableBrowser: function()
{
if (!this.currentTable) {
this.views.browse.contentElement.removeChildren();
return;
}
var panel = this;
var query = "SELECT * FROM " + this.currentTable;
this.resource.database.transaction(function(tx)
{
tx.executeSql(query, [], function(tx, result) { panel.browseQueryFinished(result) }, function(tx, error) { panel.browseQueryError(error) });
}, function(tx, error) { panel.browseQueryError(error) });
},
browseQueryFinished: function(result)
{
this.views.browse.contentElement.removeChildren();
var table = this._tableForResult(result);
if (!table) {
var emptyMsgElement = document.createElement("div");
emptyMsgElement.className = "database-table-empty";
emptyMsgElement.textContent = WebInspector.UIString("The “%s”\ntable is empty.", this.currentTable);
this.views.browse.contentElement.appendChild(emptyMsgElement);
return;
}
var rowCount = table.getElementsByTagName("tr").length;
var columnCount = table.getElementsByTagName("tr").item(0).getElementsByTagName("th").length;
var tr = document.createElement("tr");
tr.className = "database-result-filler-row";
table.appendChild(tr);
if (!(rowCount % 2))
tr.addStyleClass("alternate");
for (var i = 0; i < columnCount; ++i) {
var td = document.createElement("td");
tr.appendChild(td);
}
table.addStyleClass("database-browse-table");
this.views.browse.contentElement.appendChild(table);
},
browseQueryError: function(error)
{
this.views.browse.contentElement.removeChildren();
var errorMsgElement = document.createElement("div");
errorMsgElement.className = "database-table-error";
errorMsgElement.textContent = WebInspector.UIString("An error occurred trying to\nread the “%s” table.", this.currentTable);
this.views.browse.contentElement.appendChild(errorMsgElement);
},
queryInputKeyDown: function(event)
{
switch (event.keyIdentifier) {
case "Enter":
this._onQueryInputEnterPressed(event);
break;
case "Up":
this._onQueryInputUpPressed(event);
break;
case "Down":
this._onQueryInputDownPressed(event);
break;
}
},
appendQueryResult: function(query, result, resultClassName)
{
var commandItem = document.createElement("li");
commandItem.className = "database-command";
var queryDiv = document.createElement("div");
queryDiv.className = "database-command-query";
queryDiv.textContent = query;
commandItem.appendChild(queryDiv);
var resultDiv = document.createElement("div");
resultDiv.className = "database-command-result";
commandItem.appendChild(resultDiv);
if (resultClassName)
resultDiv.addStyleClass(resultClassName);
if (typeof result === "string" || result instanceof String)
resultDiv.textContent = result;
else if (result && result.nodeName)
resultDiv.appendChild(result);
this.views.query.commandListElement.appendChild(commandItem);
commandItem.scrollIntoView(false);
},
queryFinished: function(query, result)
{
this.appendQueryResult(query, this._tableForResult(result));
},
queryError: function(query, error)
{
if (this.currentView !== this.views.query)
this.currentView = this.views.query;
if (error.code == 1)
var message = error.message;
else if (error.code == 2)
var message = WebInspector.UIString("Database no longer has expected version.");
else
var message = WebInspector.UIString("An unexpected error %s occured.", error.code);
this.appendQueryResult(query, message, "error");
},
_onQueryInputEnterPressed: function(event)
{
event.preventDefault();
event.stopPropagation();
var query = this.queryPromptElement.value;
if (!query.length)
return;
var panel = this;
this.resource.database.transaction(function(tx)
{
tx.executeSql(query, [], function(tx, result) { panel.queryFinished(query, result) }, function(tx, error) { panel.queryError(query, error) });
}, function(tx, error) { panel.queryError(query, error) });
this.queryPromptHistory.push(query);
this.queryPromptHistoryOffset = 0;
this.queryPromptElement.value = "";
if (query.match(/^select /i)) {
if (this.currentView !== this.views.query)
this.currentView = this.views.query;
} else {
if (query.match(/^create /i) || query.match(/^drop table /i))
this.updateTableList();
// FIXME: we should only call updateTableBrowser() is we know the current table was modified
this.updateTableBrowser();
}
},
_onQueryInputUpPressed: function(event)
{
event.preventDefault();
event.stopPropagation();
if (this.queryPromptHistoryOffset == this.queryPromptHistory.length)
return;
if (this.queryPromptHistoryOffset == 0)
this.tempSavedQuery = this.queryPromptElement.value;
++this.queryPromptHistoryOffset;
this.queryPromptElement.value = this.queryPromptHistory[this.queryPromptHistory.length - this.queryPromptHistoryOffset];
this.queryPromptElement.moveCursorToEnd();
},
_onQueryInputDownPressed: function(event)
{
event.preventDefault();
event.stopPropagation();
if (this.queryPromptHistoryOffset == 0)
return;
--this.queryPromptHistoryOffset;
if (this.queryPromptHistoryOffset == 0) {
this.queryPromptElement.value = this.tempSavedQuery;
this.queryPromptElement.moveCursorToEnd();
delete this.tempSavedQuery;
return;
}
this.queryPromptElement.value = this.queryPromptHistory[this.queryPromptHistory.length - this.queryPromptHistoryOffset];
this.queryPromptElement.moveCursorToEnd();
},
_tableForResult: function(result)
{
if (!result.rows.length)
return null;
var rows = result.rows;
var length = rows.length;
var columnWidths = [];
var table = document.createElement("table");
table.className = "database-result-table";
var headerRow = document.createElement("tr");
table.appendChild(headerRow);
var j = 0;
for (var column in rows.item(0)) {
var th = document.createElement("th");
headerRow.appendChild(th);
var div = document.createElement("div");
div.textContent = column;
div.title = column;
th.appendChild(div);
columnWidths[j++] = column.length;
}
for (var i = 0; i < length; ++i) {
var row = rows.item(i);
var tr = document.createElement("tr");
if (i % 2)
tr.className = "alternate";
table.appendChild(tr);
var j = 0;
for (var column in row) {
var td = document.createElement("td");
tr.appendChild(td);
var text = row[column];
var div = document.createElement("div");
div.textContent = text;
div.title = text;
td.appendChild(div);
if (text.length > columnWidths[j])
columnWidths[j] = text.length;
++j;
}
}
var totalColumnWidths = 0;
length = columnWidths.length;
for (var i = 0; i < length; ++i)
totalColumnWidths += columnWidths[i];
// Calculate the percentage width for the columns.
var minimumPrecent = 5;
var recoupPercent = 0;
for (var i = 0; i < length; ++i) {
columnWidths[i] = Math.round((columnWidths[i] / totalColumnWidths) * 100);
if (columnWidths[i] < minimumPrecent) {
recoupPercent += (minimumPrecent - columnWidths[i]);
columnWidths[i] = minimumPrecent;
}
}
// Enforce the minimum percentage width.
while (recoupPercent > 0) {
for (var i = 0; i < length; ++i) {
if (columnWidths[i] > minimumPrecent) {
--columnWidths[i];
--recoupPercent;
if (!recoupPercent)
break;
}
}
}
length = headerRow.childNodes.length;
for (var i = 0; i < length; ++i) {
var th = headerRow.childNodes[i];
th.style.width = columnWidths[i] + "%";
}
return table;
}
}
})();
WebInspector.DatabasePanel.prototype.__proto__ = WebInspector.ResourcePanel.prototype;