Home | History | Annotate | Download | only in ui
      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 }
    265