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 } 261