1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.isBeingEdited = function(element) 27{ 28 while (element) { 29 if (element.__editing) 30 return true; 31 element = element.parentNode; 32 } 33 34 return false; 35} 36 37WebInspector.markBeingEdited = function(element, value) 38{ 39 if (value) { 40 if (element.__editing) 41 return false; 42 element.__editing = true; 43 WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1; 44 } else { 45 if (!element.__editing) 46 return false; 47 delete element.__editing; 48 --WebInspector.__editingCount; 49 } 50 return true; 51} 52 53WebInspector.isEditingAnyField = function() 54{ 55 return !!WebInspector.__editingCount; 56} 57 58WebInspector.isEventTargetAnEditableField = function(event) 59{ 60 const textInputTypes = {"text": true, "search": true, "tel": true, "url": true, "email": true, "password": true}; 61 if (event.target instanceof HTMLInputElement) 62 return event.target.type in textInputTypes; 63 64 var codeMirrorEditorElement = event.target.enclosingNodeOrSelfWithClass("CodeMirror"); 65 if (codeMirrorEditorElement && codeMirrorEditorElement.CodeMirror) 66 return !codeMirrorEditorElement.CodeMirror.getOption("readOnly"); 67 68 if (event.target instanceof HTMLTextAreaElement) 69 return true; 70 71 if (event.target.enclosingNodeOrSelfWithClass("text-prompt")) 72 return true; 73 74 return false; 75} 76 77WebInspector.EditingConfig = function(commitHandler, cancelHandler, context) 78{ 79 this.commitHandler = commitHandler; 80 this.cancelHandler = cancelHandler; 81 this.context = context; 82 this.pasteHandler; 83 this.multiline; 84 this.customFinishHandler; 85 this.spellcheck = false; 86} 87 88WebInspector.EditingConfig.prototype = { 89 setPasteHandler: function(pasteHandler) 90 { 91 this.pasteHandler = pasteHandler; 92 }, 93 94 setMultiline: function(multiline) 95 { 96 this.multiline = multiline; 97 }, 98 99 setCustomFinishHandler: function(customFinishHandler) 100 { 101 this.customFinishHandler = customFinishHandler; 102 } 103} 104 105WebInspector.startEditing = function(element, config) 106{ 107 if (!WebInspector.markBeingEdited(element, true)) 108 return; 109 110 config = config || new WebInspector.EditingConfig(function() {}, function() {}); 111 var committedCallback = config.commitHandler; 112 var cancelledCallback = config.cancelHandler; 113 var pasteCallback = config.pasteHandler; 114 var context = config.context; 115 var oldText = getContent(element); 116 var moveDirection = ""; 117 118 element.classList.add("editing"); 119 120 var oldSpellCheck = element.hasAttribute("spellcheck") ? element.spellcheck : undefined; 121 element.spellcheck = config.spellcheck; 122 123 if (config.multiline) 124 element.classList.add("multiline"); 125 126 var oldTabIndex = element.tabIndex; 127 if (element.tabIndex < 0) 128 element.tabIndex = 0; 129 130 function blurEventListener() { 131 editingCommitted.call(element); 132 } 133 134 function getContent(element) { 135 if (element.tagName === "INPUT" && element.type === "text") 136 return element.value; 137 else 138 return element.textContent; 139 } 140 141 function cleanUpAfterEditing() 142 { 143 WebInspector.markBeingEdited(element, false); 144 145 this.classList.remove("editing"); 146 this.scrollTop = 0; 147 this.scrollLeft = 0; 148 149 if (oldSpellCheck === undefined) 150 element.removeAttribute("spellcheck"); 151 else 152 element.spellcheck = oldSpellCheck; 153 154 if (oldTabIndex === -1) 155 this.removeAttribute("tabindex"); 156 else 157 this.tabIndex = oldTabIndex; 158 159 element.removeEventListener("blur", blurEventListener, false); 160 element.removeEventListener("keydown", keyDownEventListener, true); 161 if (pasteCallback) 162 element.removeEventListener("paste", pasteEventListener, true); 163 164 WebInspector.restoreFocusFromElement(element); 165 } 166 167 function editingCancelled() 168 { 169 if (this.tagName === "INPUT" && this.type === "text") 170 this.value = oldText; 171 else 172 this.textContent = oldText; 173 174 cleanUpAfterEditing.call(this); 175 176 cancelledCallback(this, context); 177 } 178 179 function editingCommitted() 180 { 181 cleanUpAfterEditing.call(this); 182 183 committedCallback(this, getContent(this), oldText, context, moveDirection); 184 } 185 186 function defaultFinishHandler(event) 187 { 188 var hasOnlyMetaModifierKey = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey; 189 if (isEnterKey(event) && (!config.multiline || hasOnlyMetaModifierKey)) 190 return "commit"; 191 else if (event.keyCode === WebInspector.KeyboardShortcut.Key.Escape.keyCode || event.keyIdentifier === "U+001B") 192 return "cancel"; 193 else if (event.keyIdentifier === "U+0009") // Tab key 194 return "move-" + (event.shiftKey ? "backward" : "forward"); 195 } 196 197 function handleEditingResult(result, event) 198 { 199 if (result === "commit") { 200 editingCommitted.call(element); 201 event.preventDefault(); 202 event.stopPropagation(); 203 } else if (result === "cancel") { 204 editingCancelled.call(element); 205 event.preventDefault(); 206 event.stopPropagation(); 207 } else if (result && result.startsWith("move-")) { 208 moveDirection = result.substring(5); 209 if (event.keyIdentifier !== "U+0009") 210 blurEventListener(); 211 } 212 } 213 214 function pasteEventListener(event) 215 { 216 var result = pasteCallback(event); 217 handleEditingResult(result, event); 218 } 219 220 function keyDownEventListener(event) 221 { 222 var handler = config.customFinishHandler || defaultFinishHandler; 223 var result = handler(event); 224 handleEditingResult(result, event); 225 } 226 227 element.addEventListener("blur", blurEventListener, false); 228 element.addEventListener("keydown", keyDownEventListener, true); 229 if (pasteCallback) 230 element.addEventListener("paste", pasteEventListener, true); 231 232 element.focus(); 233 234 return { 235 cancel: editingCancelled.bind(element), 236 commit: editingCommitted.bind(element) 237 }; 238} 239