Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / ui / InplaceEditor.js
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  */
8 WebInspector.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  */
17 WebInspector.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
27 WebInspector.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  */
208 WebInspector.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
233 WebInspector.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 }