• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @constructor
7 */
8WebInspector.InplaceEditor = function()
9{
10};
11
12/**
13 * @param {!Element} element
14 * @param {!WebInspector.InplaceEditor.Config=} config
15 * @return {?{cancel: function(), commit: function(), setWidth: function(number)}}
16 */
17WebInspector.InplaceEditor.startEditing = function(element, config)
18{
19    if (config.multiline)
20        return WebInspector.moduleManager.instance(WebInspector.InplaceEditor).startEditing(element, config);
21
22    if (!WebInspector.InplaceEditor._defaultInstance)
23        WebInspector.InplaceEditor._defaultInstance = new WebInspector.InplaceEditor();
24    return WebInspector.InplaceEditor._defaultInstance.startEditing(element, config);
25}
26
27WebInspector.InplaceEditor.prototype = {
28    /**
29     * @return {string}
30     */
31    editorContent: function(editingContext) {
32        var element = editingContext.element;
33        if (element.tagName === "INPUT" && element.type === "text")
34            return element.value;
35
36        return element.textContent;
37    },
38
39    setUpEditor: function(editingContext)
40    {
41        var element = editingContext.element;
42        element.classList.add("editing");
43
44        var oldTabIndex = element.getAttribute("tabIndex");
45        if (typeof oldTabIndex !== "number" || oldTabIndex < 0)
46            element.tabIndex = 0;
47        WebInspector.setCurrentFocusElement(element);
48        editingContext.oldTabIndex = oldTabIndex;
49    },
50
51    closeEditor: function(editingContext)
52    {
53        var element = editingContext.element;
54        element.classList.remove("editing");
55
56        if (typeof editingContext.oldTabIndex !== "number")
57            element.removeAttribute("tabIndex");
58        else
59            element.tabIndex = editingContext.oldTabIndex;
60        element.scrollTop = 0;
61        element.scrollLeft = 0;
62    },
63
64    cancelEditing: function(editingContext)
65    {
66        var element = editingContext.element;
67        if (element.tagName === "INPUT" && element.type === "text")
68            element.value = editingContext.oldText;
69        else
70            element.textContent = editingContext.oldText;
71    },
72
73    augmentEditingHandle: function(editingContext, handle)
74    {
75    },
76
77    /**
78     * @param {!Element} element
79     * @param {!WebInspector.InplaceEditor.Config=} config
80     * @return {?{cancel: function(), commit: function()}}
81     */
82    startEditing: function(element, config)
83    {
84        if (!WebInspector.markBeingEdited(element, true))
85            return null;
86
87        config = config || new WebInspector.InplaceEditor.Config(function() {}, function() {});
88        var editingContext = { element: element, config: config };
89        var committedCallback = config.commitHandler;
90        var cancelledCallback = config.cancelHandler;
91        var pasteCallback = config.pasteHandler;
92        var context = config.context;
93        var isMultiline = config.multiline || false;
94        var moveDirection = "";
95        var self = this;
96
97        /**
98         * @param {?Event} e
99         */
100        function consumeCopy(e)
101        {
102            e.consume();
103        }
104
105        this.setUpEditor(editingContext);
106
107        editingContext.oldText = isMultiline ? config.initialValue : this.editorContent(editingContext);
108
109        /**
110         * @param {?Event=} e
111         */
112        function blurEventListener(e) {
113            if (!isMultiline || !e || !e.relatedTarget || !e.relatedTarget.isSelfOrDescendant(element))
114                editingCommitted.call(element);
115        }
116
117        function cleanUpAfterEditing()
118        {
119            WebInspector.markBeingEdited(element, false);
120
121            element.removeEventListener("blur", blurEventListener, isMultiline);
122            element.removeEventListener("keydown", keyDownEventListener, true);
123            if (pasteCallback)
124                element.removeEventListener("paste", pasteEventListener, true);
125
126            WebInspector.restoreFocusFromElement(element);
127            self.closeEditor(editingContext);
128        }
129
130        /** @this {Element} */
131        function editingCancelled()
132        {
133            self.cancelEditing(editingContext);
134            cleanUpAfterEditing();
135            cancelledCallback(this, context);
136        }
137
138        /** @this {Element} */
139        function editingCommitted()
140        {
141            cleanUpAfterEditing();
142
143            committedCallback(this, self.editorContent(editingContext), editingContext.oldText, context, moveDirection);
144        }
145
146        function defaultFinishHandler(event)
147        {
148            var isMetaOrCtrl = WebInspector.isMac() ?
149                event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
150                event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
151            if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !isMultiline || isMetaOrCtrl))
152                return "commit";
153            else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
154                return "cancel";
155            else if (!isMultiline && event.keyIdentifier === "U+0009") // Tab key
156                return "move-" + (event.shiftKey ? "backward" : "forward");
157        }
158
159        function handleEditingResult(result, event)
160        {
161            if (result === "commit") {
162                editingCommitted.call(element);
163                event.consume(true);
164            } else if (result === "cancel") {
165                editingCancelled.call(element);
166                event.consume(true);
167            } else if (result && result.startsWith("move-")) {
168                moveDirection = result.substring(5);
169                if (event.keyIdentifier !== "U+0009")
170                    blurEventListener();
171            }
172        }
173
174        function pasteEventListener(event)
175        {
176            var result = pasteCallback(event);
177            handleEditingResult(result, event);
178        }
179
180        function keyDownEventListener(event)
181        {
182            var handler = config.customFinishHandler || defaultFinishHandler;
183            var result = handler(event);
184            handleEditingResult(result, event);
185        }
186
187        element.addEventListener("blur", blurEventListener, isMultiline);
188        element.addEventListener("keydown", keyDownEventListener, true);
189        if (pasteCallback)
190            element.addEventListener("paste", pasteEventListener, true);
191
192        var handle = {
193            cancel: editingCancelled.bind(element),
194            commit: editingCommitted.bind(element)
195        };
196        this.augmentEditingHandle(editingContext, handle);
197        return handle;
198    }
199}
200
201/**
202 * @constructor
203 * @param {function(!Element,string,string,T,string)} commitHandler
204 * @param {function(!Element,T)} cancelHandler
205 * @param {T=} context
206 * @template T
207 */
208WebInspector.InplaceEditor.Config = function(commitHandler, cancelHandler, context)
209{
210    this.commitHandler = commitHandler;
211    this.cancelHandler = cancelHandler
212    this.context = context;
213
214    /**
215     * Handles the "paste" event, return values are the same as those for customFinishHandler
216     * @type {function(!Element)|undefined}
217     */
218    this.pasteHandler;
219
220    /**
221     * Whether the edited element is multiline
222     * @type {boolean|undefined}
223     */
224    this.multiline;
225
226    /**
227     * Custom finish handler for the editing session (invoked on keydown)
228     * @type {function(!Element,*)|undefined}
229     */
230    this.customFinishHandler;
231}
232
233WebInspector.InplaceEditor.Config.prototype = {
234    setPasteHandler: function(pasteHandler)
235    {
236        this.pasteHandler = pasteHandler;
237    },
238
239    /**
240     * @param {string} initialValue
241     * @param {!Object} mode
242     * @param {string} theme
243     * @param {boolean=} lineWrapping
244     * @param {boolean=} smartIndent
245     */
246    setMultilineOptions: function(initialValue, mode, theme, lineWrapping, smartIndent)
247    {
248        this.multiline = true;
249        this.initialValue = initialValue;
250        this.mode = mode;
251        this.theme = theme;
252        this.lineWrapping = lineWrapping;
253        this.smartIndent = smartIndent;
254    },
255
256    setCustomFinishHandler: function(customFinishHandler)
257    {
258        this.customFinishHandler = customFinishHandler;
259    }
260}
261