1/*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26/**
27 * @constructor
28 * @extends {WebInspector.View}
29 */
30WebInspector.DatabaseQueryView = function(database)
31{
32    WebInspector.View.call(this);
33
34    this.database = database;
35
36    this.element.classList.add("storage-view");
37    this.element.classList.add("query");
38    this.element.classList.add("monospace");
39    this.element.addEventListener("selectstart", this._selectStart.bind(this), false);
40
41    this._promptElement = document.createElement("div");
42    this._promptElement.className = "database-query-prompt";
43    this._promptElement.appendChild(document.createElement("br"));
44    this._promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true);
45    this.element.appendChild(this._promptElement);
46
47    this.prompt = new WebInspector.TextPromptWithHistory(this.completions.bind(this), " ");
48    this.prompt.attach(this._promptElement);
49
50    this.element.addEventListener("click", this._messagesClicked.bind(this), true);
51}
52
53WebInspector.DatabaseQueryView.Events = {
54    SchemaUpdated: "SchemaUpdated"
55}
56
57WebInspector.DatabaseQueryView.prototype = {
58    _messagesClicked: function()
59    {
60        if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
61            this.prompt.moveCaretToEndOfPrompt();
62    },
63
64    /**
65     * @param {!Element} proxyElement
66     * @param {!Range} wordRange
67     * @param {boolean} force
68     * @param {function(!Array.<string>, number=)} completionsReadyCallback
69     */
70    completions: function(proxyElement, wordRange, force, completionsReadyCallback)
71    {
72        var prefix = wordRange.toString().toLowerCase();
73        if (!prefix)
74            return;
75        var results = [];
76
77        function accumulateMatches(textArray)
78        {
79            for (var i = 0; i < textArray.length; ++i) {
80                var text = textArray[i].toLowerCase();
81                if (text.length < prefix.length)
82                    continue;
83                if (!text.startsWith(prefix))
84                    continue;
85                results.push(textArray[i]);
86            }
87        }
88
89        function tableNamesCallback(tableNames)
90        {
91            accumulateMatches(tableNames.map(function(name) { return name + " " }));
92            accumulateMatches(["SELECT ", "FROM ", "WHERE ", "LIMIT ", "DELETE FROM ", "CREATE ", "DROP ", "TABLE ", "INDEX ", "UPDATE ", "INSERT INTO ", "VALUES ("]);
93
94            completionsReadyCallback(results);
95        }
96        this.database.getTableNames(tableNamesCallback);
97    },
98
99    _selectStart: function(event)
100    {
101        if (this._selectionTimeout)
102            clearTimeout(this._selectionTimeout);
103
104        this.prompt.clearAutoComplete();
105
106        /**
107         * @this {WebInspector.DatabaseQueryView}
108         */
109        function moveBackIfOutside()
110        {
111            delete this._selectionTimeout;
112            if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed)
113                this.prompt.moveCaretToEndOfPrompt();
114            this.prompt.autoCompleteSoon();
115        }
116
117        this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100);
118    },
119
120    _promptKeyDown: function(event)
121    {
122        if (isEnterKey(event)) {
123            this._enterKeyPressed(event);
124            return;
125        }
126    },
127
128    _enterKeyPressed: function(event)
129    {
130        event.consume(true);
131
132        this.prompt.clearAutoComplete(true);
133
134        var query = this.prompt.text;
135        if (!query.length)
136            return;
137
138        this.prompt.pushHistoryItem(query);
139        this.prompt.text = "";
140
141        this.database.executeSql(query, this._queryFinished.bind(this, query), this._queryError.bind(this, query));
142    },
143
144    _queryFinished: function(query, columnNames, values)
145    {
146        var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, values);
147        var trimmedQuery = query.trim();
148
149        if (dataGrid) {
150            dataGrid.renderInline();
151            this._appendViewQueryResult(trimmedQuery, dataGrid);
152            dataGrid.autoSizeColumns(5);
153        }
154
155        if (trimmedQuery.match(/^create /i) || trimmedQuery.match(/^drop table /i))
156            this.dispatchEventToListeners(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this.database);
157    },
158
159    _queryError: function(query, errorMessage)
160    {
161        this._appendErrorQueryResult(query, errorMessage);
162    },
163
164    /**
165     * @param {string} query
166     * @param {!WebInspector.View} view
167     */
168    _appendViewQueryResult: function(query, view)
169    {
170        var resultElement = this._appendQueryResult(query);
171        view.show(resultElement);
172
173        this._promptElement.scrollIntoView(false);
174    },
175
176    /**
177     * @param {string} query
178     * @param {string} errorText
179     */
180    _appendErrorQueryResult: function(query, errorText)
181    {
182        var resultElement = this._appendQueryResult(query);
183        resultElement.classList.add("error")
184        resultElement.textContent = errorText;
185
186        this._promptElement.scrollIntoView(false);
187    },
188
189    _appendQueryResult: function(query)
190    {
191        var element = document.createElement("div");
192        element.className = "database-user-query";
193        this.element.insertBefore(element, this.prompt.proxyElement);
194
195        var commandTextElement = document.createElement("span");
196        commandTextElement.className = "database-query-text";
197        commandTextElement.textContent = query;
198        element.appendChild(commandTextElement);
199
200        var resultElement = document.createElement("div");
201        resultElement.className = "database-query-result";
202        element.appendChild(resultElement);
203        return resultElement;
204    },
205
206    __proto__: WebInspector.View.prototype
207}
208