Upstream version 10.39.225.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 self.runtime.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 (config.blurHandler && !config.blurHandler(element, e))
114                 return;
115             if (!isMultiline || !e || !e.relatedTarget || !e.relatedTarget.isSelfOrDescendant(element))
116                 editingCommitted.call(element);
117         }
118
119         function cleanUpAfterEditing()
120         {
121             WebInspector.markBeingEdited(element, false);
122
123             element.removeEventListener("blur", blurEventListener, isMultiline);
124             element.removeEventListener("keydown", keyDownEventListener, true);
125             if (pasteCallback)
126                 element.removeEventListener("paste", pasteEventListener, true);
127
128             WebInspector.restoreFocusFromElement(element);
129             self.closeEditor(editingContext);
130         }
131
132         /** @this {Element} */
133         function editingCancelled()
134         {
135             self.cancelEditing(editingContext);
136             cleanUpAfterEditing();
137             cancelledCallback(this, context);
138         }
139
140         /** @this {Element} */
141         function editingCommitted()
142         {
143             cleanUpAfterEditing();
144
145             committedCallback(this, self.editorContent(editingContext), editingContext.oldText, context, moveDirection);
146         }
147
148         function defaultFinishHandler(event)
149         {
150             var isMetaOrCtrl = WebInspector.isMac() ?
151                 event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
152                 event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
153             if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !isMultiline || isMetaOrCtrl))
154                 return "commit";
155             else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
156                 return "cancel";
157             else if (!isMultiline && event.keyIdentifier === "U+0009") // Tab key
158                 return "move-" + (event.shiftKey ? "backward" : "forward");
159         }
160
161         function handleEditingResult(result, event)
162         {
163             if (result === "commit") {
164                 editingCommitted.call(element);
165                 event.consume(true);
166             } else if (result === "cancel") {
167                 editingCancelled.call(element);
168                 event.consume(true);
169             } else if (result && result.startsWith("move-")) {
170                 moveDirection = result.substring(5);
171                 if (event.keyIdentifier !== "U+0009")
172                     blurEventListener();
173             }
174         }
175
176         function pasteEventListener(event)
177         {
178             var result = pasteCallback(event);
179             handleEditingResult(result, event);
180         }
181
182         function keyDownEventListener(event)
183         {
184             var handler = config.customFinishHandler || defaultFinishHandler;
185             var result = handler(event);
186             handleEditingResult(result, event);
187         }
188
189         element.addEventListener("blur", blurEventListener, isMultiline);
190         element.addEventListener("keydown", keyDownEventListener, true);
191         if (pasteCallback)
192             element.addEventListener("paste", pasteEventListener, true);
193
194         var handle = {
195             cancel: editingCancelled.bind(element),
196             commit: editingCommitted.bind(element)
197         };
198         this.augmentEditingHandle(editingContext, handle);
199         return handle;
200     }
201 }
202
203 /**
204  * @constructor
205  * @param {function(!Element,string,string,T,string)} commitHandler
206  * @param {function(!Element,T)} cancelHandler
207  * @param {T=} context
208  * @param {function(!Element,!Event):boolean=} blurHandler
209  * @template T
210  */
211 WebInspector.InplaceEditor.Config = function(commitHandler, cancelHandler, context, blurHandler)
212 {
213     this.commitHandler = commitHandler;
214     this.cancelHandler = cancelHandler
215     this.context = context;
216     this.blurHandler = blurHandler;
217
218     /**
219      * Handles the "paste" event, return values are the same as those for customFinishHandler
220      * @type {function(!Element)|undefined}
221      */
222     this.pasteHandler;
223
224     /**
225      * Whether the edited element is multiline
226      * @type {boolean|undefined}
227      */
228     this.multiline;
229
230     /**
231      * Custom finish handler for the editing session (invoked on keydown)
232      * @type {function(!Element,*)|undefined}
233      */
234     this.customFinishHandler;
235 }
236
237 WebInspector.InplaceEditor.Config.prototype = {
238     setPasteHandler: function(pasteHandler)
239     {
240         this.pasteHandler = pasteHandler;
241     },
242
243     /**
244      * @param {string} initialValue
245      * @param {!Object} mode
246      * @param {string} theme
247      * @param {boolean=} lineWrapping
248      * @param {boolean=} smartIndent
249      */
250     setMultilineOptions: function(initialValue, mode, theme, lineWrapping, smartIndent)
251     {
252         this.multiline = true;
253         this.initialValue = initialValue;
254         this.mode = mode;
255         this.theme = theme;
256         this.lineWrapping = lineWrapping;
257         this.smartIndent = smartIndent;
258     },
259
260     setCustomFinishHandler: function(customFinishHandler)
261     {
262         this.customFinishHandler = customFinishHandler;
263     }
264 }