1/*
2 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
3 * Copyright (C) 2013 Samsung Electronics. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27WebInspector.DOMStorageContentView = function(representedObject)
28{
29    WebInspector.ContentView.call(this, representedObject);
30
31    this.element.classList.add(WebInspector.DOMStorageContentView.StyleClassName);
32
33    representedObject.addEventListener(WebInspector.DOMStorageObject.Event.ItemsCleared, this.itemsCleared, this);
34    representedObject.addEventListener(WebInspector.DOMStorageObject.Event.ItemAdded, this.itemAdded, this);
35    representedObject.addEventListener(WebInspector.DOMStorageObject.Event.ItemRemoved, this.itemRemoved, this);
36    representedObject.addEventListener(WebInspector.DOMStorageObject.Event.ItemUpdated, this.itemUpdated, this);
37
38    this.reset();
39};
40
41WebInspector.DOMStorageContentView.StyleClassName = "dom-storage";
42WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName = "duplicate-key";
43WebInspector.DOMStorageContentView.MissingKeyStyleClassName = "missing-key";
44WebInspector.DOMStorageContentView.MissingValueStyleClassName = "missing-value";
45
46
47WebInspector.DOMStorageContentView.prototype = {
48    constructor: WebInspector.DOMStorageContentView,
49    __proto__: WebInspector.ContentView.prototype,
50
51    // Public
52
53    reset: function()
54    {
55        this.representedObject.getEntries(function(error, entries) {
56            if (error)
57                return;
58
59            if (!this._dataGrid) {
60                var columns = {};
61                columns.key = {title: WebInspector.UIString("Key"), sortable: true};
62                columns.value = {title: WebInspector.UIString("Value"), sortable: true};
63
64                this._dataGrid = new WebInspector.DataGrid(columns, this._editingCallback.bind(this), this._deleteCallback.bind(this));
65                this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, this._sortDataGrid, this);
66
67                this.element.appendChild(this._dataGrid.element);
68            }
69
70            console.assert(this._dataGrid);
71
72            var nodes = [];
73            for (var entry of entries) {
74                if (!entry[0] || !entry[1])
75                    continue;
76                var data = {key: entry[0], value: entry[1]};
77                var node = new WebInspector.DataGridNode(data, false);
78                node.selectable = true;
79                this._dataGrid.appendChild(node);
80            }
81
82            this._sortDataGrid();
83            this._dataGrid.addPlaceholderNode();
84            this._dataGrid.updateLayout();
85        }.bind(this));
86    },
87
88    saveToCookie: function(cookie)
89    {
90        cookie.type = WebInspector.ContentViewCookieType.DOMStorage;
91        cookie.isLocalStorage = this.representedObject.isLocalStorage();
92        cookie.host = this.representedObject.host;
93    },
94
95    itemsCleared: function(event)
96    {
97        this._dataGrid.removeChildren();
98        this._dataGrid.addPlaceholderNode();
99    },
100
101    itemRemoved: function(event)
102    {
103        for (var node of this._dataGrid.children) {
104            if (node.data.key === event.data.key)
105                return this._dataGrid.removeChild(node);
106        }
107    },
108
109    itemAdded: function(event)
110    {
111        var key = event.data.key;
112        var value = event.data.value;
113
114        // Enforce key uniqueness.
115        for (var node of this._dataGrid.children) {
116            if (node.data.key === key)
117                return;
118        }
119
120        var data = {key: key, value: value};
121        this._dataGrid.appendChild(new WebInspector.DataGridNode(data, false));
122        this._sortDataGrid();
123    },
124
125    itemUpdated: function(event)
126    {
127        var key = event.data.key;
128        var value = event.data.value;
129
130        var keyFound = false;
131        for (var childNode of this._dataGrid.children) {
132            if (childNode.data.key === key) {
133                // Remove any rows that are now duplicates.
134                if (keyFound) {
135                    this._dataGrid.removeChild(childNode);
136                    continue;
137                }
138
139                keyFound = true;
140                childNode.data.value = value;
141                childNode.refresh();
142            }
143        }
144        this._sortDataGrid();
145    },
146
147    updateLayout: function()
148    {
149        if (this._dataGrid)
150            this._dataGrid.updateLayout();
151    },
152
153    get scrollableElements()
154    {
155        if (!this._dataGrid)
156            return [];
157        return [this._dataGrid.scrollContainer];
158    },
159
160    // Private
161
162    _sortDataGrid: function()
163    {
164        if (!this._dataGrid.sortOrder)
165            return;
166
167        var sortColumnIdentifier = this._dataGrid.sortColumnIdentifier || "key";
168
169        function comparator(a, b)
170        {
171            return b.data[sortColumnIdentifier].localeCompare(a.data[sortColumnIdentifier]);
172        }
173
174        this._dataGrid.sortNodes(comparator, this._dataGrid.sortOrder);
175    },
176
177    _deleteCallback: function(node)
178    {
179        if (!node || node.isPlaceholderNode)
180            return;
181
182        this._dataGrid.removeChild(node);
183        this.representedObject.removeItem(node.data["key"]);
184    },
185
186    _editingCallback: function(editingNode, columnIdentifier, oldText, newText, moveDirection)
187    {
188        var key = editingNode.data["key"].trim();
189        var value = editingNode.data["value"].trim();
190        var previousValue = oldText.trim();
191        var enteredValue = newText.trim();
192        var columnIndex = this._dataGrid.orderedColumns.indexOf(columnIdentifier);
193        var mayMoveToNextRow = moveDirection === "forward" && columnIndex == this._dataGrid.orderedColumns.length - 1;
194        var mayMoveToPreviousRow = moveDirection === "backward" && columnIndex == 0;
195        var willMoveRow = mayMoveToNextRow || mayMoveToPreviousRow;
196        var shouldCommitRow = willMoveRow && key.length && value.length;
197
198        // Remove the row if its values are newly cleared, and it's not a placeholder.
199        if (!key.length && !value.length && willMoveRow) {
200            if (previousValue.length && !editingNode.isPlaceholderNode)
201                this._dataGrid.removeChild(editingNode);
202            return;
203        }
204
205        // If the key field was deleted, restore it when committing the row.
206        if (key === enteredValue && !key.length) {
207            if (willMoveRow && !editingNode.isPlaceholderNode) {
208                editingNode.data.key = previousValue;
209                editingNode.refresh();
210            } else
211                editingNode.element.classList.add(WebInspector.DOMStorageContentView.MissingKeyStyleClassName);
212        } else if (key.length) {
213            editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingKeyStyleClassName);
214            editingNode.__previousKeyValue = previousValue;
215        }
216
217        if (value === enteredValue && !value.length)
218            editingNode.element.classList.add(WebInspector.DOMStorageContentView.MissingValueStyleClassName);
219        else
220            editingNode.element.classList.remove(WebInspector.DOMStorageContentView.MissingValueStyleClassName);
221
222        if (editingNode.isPlaceholderNode && previousValue !== enteredValue)
223            this._dataGrid.addPlaceholderNode();
224
225        if (!shouldCommitRow)
226            return; // One of the inputs is missing, or we aren't moving between rows.
227
228        var domStorage = this.representedObject;
229        if (domStorage.entries.has(key)) {
230            editingNode.element.classList.add(WebInspector.DOMStorageContentView.DuplicateKeyStyleClassName);
231            return;
232        }
233
234        editingNode.element.classList.remove(WebInspector.DOMStorageContentView.DuplicateKeySyleClassName);
235
236        if (editingNode.__previousKeyValue != key)
237            domStorage.removeItem(editingNode.__previousKeyValue);
238
239        domStorage.setItem(key, value);
240        // The table will be re-sorted when the backend fires the itemUpdated event.
241    }
242};
243