Changeset 1394 for trunk/modules


Ignore:
Timestamp:
02/10/18 06:36:44 (2 years ago)
Author:
gogo
Message:

Rewrite and standardise key events (see #1393), add onKeyDown, onKeyUp and also onShortCut

This commit rewrites much of the keyboard event handling, but it is (hopefully, in theory, as far as I can test) backwards compatible, in fact, it's so compatible it is used in the exact same way so far as plugins are concerned.

It adds events for plugins of onKeyDown, onKeyUp and also onShortCut

The existing onKeyPress is still fired - however with some caveat, namely the intention is that onKeyPress is only fired for the following classifications of key pressing...

"Adding Characters"

letters, numbers, space, punctuation, symbols, tab, enter

"Deleting Characters"

delete, backspace

"Shortcuts and Other Useful Things"

esc, ctrl-a, ctrl-b...


where previously under some browsers (WebKit?, IE) it would be fired for all keydown events, now it will only fire for "normally useful" events, and the event noise is reduced.

Also considerable work to make sure that the browsers are consistent, to ensure this some "keydown" events in some browsers also produce a "fake" keypress event, where in other browsers they would get a real keypress event (and we want them to).

getKey() hs been improved in Gecko and WebKit? and should be about the same for most uses (certainly all uses in the Xinha distribution), as it now uses the DOM3 keyboard event codes, special keys are now returned by name (where possible), eg tab is reported by getKey as "Tab", where previously, it would have been probably an actual tab character (but other special keys would have been pretty random incorrect characters).

Whew. This was a lot more work than I had anticipated. KeyboardEvent? across browsers is a mess.


Location:
trunk/modules
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/modules/Gecko/Gecko.js

    r1378 r1394  
    5050/** Allow Gecko to handle some key events in a special way. 
    5151 */ 
    52    
    5352Gecko.prototype.onKeyPress = function(ev) 
    5453{ 
    5554  var editor = this.editor; 
    5655  var s = editor.getSelection(); 
    57    
    5856  // Handle shortcuts 
    5957  if(editor.isShortCut(ev)) 
     
    133131        return a; 
    134132      }; 
    135    
     133 
    136134      if ( editor.config.convertUrlsToLinks && s && s.isCollapsed && s.anchorNode.nodeType == 3 && s.anchorNode.data.length > 3 && s.anchorNode.data.indexOf('.') >= 0 ) 
    137135      { 
     
    157155 
    158156          autoWrap(midTextEmail, 'a').href = 'mailto:' + mEmail[0]; 
    159           break; 
     157          return true; 
    160158        } 
    161159 
     
    177175          var midTextUrl   = leftTextUrl.splitText(midStart); 
    178176          autoWrap(midTextUrl, 'a').href = (mUrl[1] ? mUrl[1] : 'http://') + mUrl[2]; 
    179           break; 
     177          return true; 
    180178        } 
    181179      } 
     
    734732}; 
    735733 
    736 /** Determine if the given event object is a keydown/press event. 
     734/** Due to browser differences, some keys which Xinha prefers to call a keyPress 
     735 *   do not get an actual keypress event.  This browser specific function  
     736 *   overridden in the browser's engine (eg modules/WebKit/WebKit.js) as required 
     737 *   takes a keydown event type and tells us if we should treat it as a  
     738 *   keypress event type. 
    737739 * 
    738  *  @param event Event  
    739  *  @returns true|false 
    740  */ 
    741   
    742 Xinha.prototype.isKeyEvent = function(event) 
    743 { 
    744   return event.type == "keypress"; 
    745 } 
     740 *  To be clear, the keys we are interested here are 
     741 *        Escape, Tab, Backspace, Delete, Enter 
     742 *   these are "non printable" characters which we still want to regard generally 
     743 *   as a keypress.   
     744 *  
     745 *  If the browser does not report these as a keypress 
     746 *   ( https://dvcs.w3.org/hg/d4e/raw-file/tip/key-event-test.html ) 
     747 *   then this function must return true for such keydown events as it is 
     748 *   given. 
     749 *  
     750 * @param KeyboardEvent with keyEvent.type == keydown 
     751 * @return boolean 
     752 */ 
     753 
     754Xinha.prototype.isKeyDownThatShouldGetButDoesNotGetAKeyPressEvent = function(keyEvent) 
     755{ 
     756  // Dom 3 
     757  if(typeof keyEvent.key != 'undefined') 
     758  { 
     759    // Found using IE11 (which uses Gecko) 
     760    //   this seems to be a reasonable way to distinguish 
     761    //   between IE11 and other Gecko browsers which do  
     762    //   not provide the "Old DOM3" .char property 
     763    if(typeof keyEvent.char != 'undefined') 
     764    { 
     765      if(typeof Xinha.DOM3_IE11_KeyDownKeyPress_RE == 'undefined') 
     766      { 
     767        // I don't know if pre-defining this is really faster in the modern world of 
     768        //  Javascript JIT compiling, but it does no harm 
     769        Xinha.DOM3_IE11_KeyDownKeyPress_RE = /^(Tab|Backspace|Del)/; 
     770      } 
     771       
     772      if(Xinha.DOM3_IE11_KeyDownKeyPress_RE.test(keyEvent.key)) 
     773      { 
     774        return true; 
     775      } 
     776    } 
     777     
     778    // Firefox reports everything we need as a keypress 
     779    // correctly (in terms of Xinha) 
     780  } 
     781  // Legacy 
     782  else 
     783  { 
     784    // Even very old firefox reports everything we need as a keypress 
     785    // correctly (in terms of Xinha) 
     786  } 
     787}; 
    746788 
    747789/** Return the character (as a string) of a keyEvent  - ie, press the 'a' key and 
     
    751793 *  @returns string 
    752794 */ 
    753                                     
     795 
    754796Xinha.prototype.getKey = function(keyEvent) 
    755 { 
    756   return String.fromCharCode(keyEvent.charCode); 
     797{  
     798  // DOM3 Key is easy (ish) 
     799  if(typeof keyEvent.key != 'undefined' && keyEvent.key.length > 0) 
     800  { 
     801    switch(keyEvent.key) 
     802    { 
     803      case 'Unidentified': 
     804        // Some old Gecko version reports Shift-Tab as Unidentified 
     805        if(typeof keyEvent.keyCode != 'undefined' && keyEvent.keyCode == 9) return 'Tab'; 
     806         
     807        // Otherwise not know 
     808        return ''; 
     809         
     810      case 'Spacebar': // FF<37 
     811        return ' ';  
     812    } 
     813     
     814    return keyEvent.key; 
     815  } 
     816  // If charCode is specified, that's what we want 
     817  else if(keyEvent.charCode) 
     818  { 
     819    return String.fromCharCode(keyEvent.charCode); 
     820  } 
     821  // Safari does not set charCode if CTRL is pressed 
     822  //  but does set keyCode to the key, it also sets keyCode 
     823  //  for the actual pressing of ctrl, skip that 
     824  //  the keyCode in Safari si the uppercase character's code  
     825  //  for that key, so if shift is not pressed, lowercase it 
     826  else if(keyEvent.ctrlKey && keyEvent.keyCode != 17) 
     827  { 
     828    if(keyEvent.shiftKey) 
     829    { 
     830      return String.fromCharCode(keyEvent.keyCode); 
     831    } 
     832    else 
     833    { 
     834      return String.fromCharCode(keyEvent.keyCode).toLowerCase(); 
     835    } 
     836  } 
     837   
     838  // Ok, give up, no idea! 
     839  return ''; 
    757840} 
    758841 
  • trunk/modules/InternetExplorer/InternetExplorer.js

    r1312 r1394  
    11 
    22  /*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:-- 
     3    -- 
     4    --  NOTICE Modern IE does not use this engine any more 
     5    -- 
     6    --          IE 11 identifies as Gecko 
     7    --          Edge  identifies as WebKit 
     8    -- 
     9    --  The last IE version to use this engine is probably IE10, people should 
     10    --   not use such an old version of IE and should upgrade or use a WebKit 
     11    --   or Gecko based browser. 
     12    -- 
     13    --  This engine is no longer officially supported or tested. 
     14    -- 
     15    ----------------------------------------------------------------------------- 
     16    -- 
    317    --  Xinha (is not htmlArea) - http://xinha.gogo.co.nz/ 
    418    -- 
     
    5064/** Allow Internet Explorer to handle some key events in a special way. 
    5165 */ 
    52    
    5366InternetExplorer.prototype.onKeyPress = function(ev) 
    5467{ 
     68  var editor = this.editor; 
     69   
    5570  // Shortcuts 
    5671  if(this.editor.isShortCut(ev)) 
     
    94109    break; 
    95110     
    96     case 9: // KEY tab, see ticket #1121 
    97     { 
     111    case 9: // KEY tab 
     112    { 
     113      // Note that the ListOperations plugin will handle tabs in list items and indent/outdent those 
     114      // at some point TableOperations might do also 
     115      // so this only has to handle a tab/untab in text 
     116      if(editor.config.tabSpanClass) 
     117      { 
     118        if(!ev.shiftKey) 
     119        { 
     120          editor.insertHTML('<span class="'+editor.config.tabSpanClass+'">'+editor.config.tabSpanContents+'</span>'); 
     121          var s = editor.getSelection(); 
     122          var r = editor.createRange(s);                    
     123          r.collapse(true); 
     124          r.select(); 
     125        } 
     126        else 
     127        { 
     128          // Shift tab is not trivial to fix in old IE 
     129          // and I don't care enough about it to try hard 
     130        } 
     131      } 
     132       
    98133      Xinha._stopEvent(ev); 
    99134      return true; 
    100135    } 
     136    break; 
    101137 
    102138  } 
     
    825861}; 
    826862 
    827 /** Determine if the given event object is a keydown/press event. 
     863/** Due to browser differences, some keys which Xinha prefers to call a keyPress 
     864 *   do not get an actual keypress event.  This browser specific function  
     865 *   overridden in the browser's engine (eg modules/WebKit/WebKit.js) as required 
     866 *   takes a keydown event type and tells us if we should treat it as a  
     867 *   keypress event type. 
    828868 * 
    829  *  @param event Event  
    830  *  @returns true|false 
    831  */ 
    832   
    833 Xinha.prototype.isKeyEvent = function(event) 
    834 { 
    835   return event.type == "keydown"; 
    836 } 
     869 *  To be clear, the keys we are interested here are 
     870 *        Escape, Tab, Backspace, Delete, Enter 
     871 *   these are "non printable" characters which we still want to regard generally 
     872 *   as a keypress.   
     873 *  
     874 *  If the browser does not report these as a keypress 
     875 *   ( https://dvcs.w3.org/hg/d4e/raw-file/tip/key-event-test.html ) 
     876 *   then this function must return true for such keydown events as it is 
     877 *   given. 
     878 *  
     879 * @param KeyboardEvent with keyEvent.type == keydown 
     880 * @return boolean 
     881 */ 
     882 
     883Xinha.prototype.isKeyDownThatShouldGetButDoesNotGetAKeyPressEvent = function(keyEvent) 
     884{ 
     885  // Dom 3 
     886  if(typeof keyEvent.key != 'undefined') 
     887  { 
     888    // Found using IE11 in 9-10 modes 
     889    if(keyEvent.key.match(/^(Tab|Backspace|Del)/)) 
     890    { 
     891      return true; 
     892    } 
     893  } 
     894  // Legacy 
     895  else 
     896  { 
     897    // Found using IE11 in 5-8 modes 
     898    if(keyEvent.keyCode == 9   // Tab 
     899    || keyEvent.keyCode == 8   // Backspace 
     900    || keyEvent.keyCode == 46  // Del 
     901    ) return true; 
     902  } 
     903}; 
    837904 
    838905/** Return the character (as a string) of a keyEvent  - ie, press the 'a' key and 
  • trunk/modules/Opera/Opera.js

    r1312 r1394  
    11 
    22  /*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:-- 
     3    -- 
     4    -- 
     5    --  NOTICE Modern Opera does not use this engine any more, it identifies as 
     6    --           and uses WebKit (Opera is these days based on Blink). 
     7    -- 
     8    --   People using older versions of Opera that need this engine should 
     9    --   upgrade to a WebKit or Gecko based browser. 
     10    -- 
     11    --  This engine is no longer officially supported or tested. 
     12    -- 
     13    ----------------------------------------------------------------------------- 
    314    --  Xinha (is not htmlArea) - http://xinha.gogo.co.nz/ 
    415    -- 
     
    5061/** Allow Opera to handle some key events in a special way. 
    5162 */ 
    52    
    5363Opera.prototype.onKeyPress = function(ev) 
    5464{ 
     
    693703}; 
    694704 
    695 /** Determine if the given event object is a keydown/press event. 
     705/**  
     706 * @NOTE I don't have a way to test this any more (don't have old opera version 
     707 *   and don't care enough to find one), I'm assuming it will probably be close 
     708 *   to Gecko so I have just copied it directly. 
     709 *  
     710 * Due to browser differences, some keys which Xinha prefers to call a keyPress 
     711 *   do not get an actual keypress event.  This browser specific function  
     712 *   overridden in the browser's engine (eg modules/WebKit/WebKit.js) as required 
     713 *   takes a keydown event type and tells us if we should treat it as a  
     714 *   keypress event type. 
    696715 * 
    697  *  @param event Event  
    698  *  @returns true|false 
    699  */ 
    700   
    701 Xinha.prototype.isKeyEvent = function(event) 
    702 { 
    703   return event.type == "keypress"; 
    704 } 
     716 *  To be clear, the keys we are interested here are 
     717 *        Escape, Tab, Backspace, Delete, Enter 
     718 *   these are "non printable" characters which we still want to regard generally 
     719 *   as a keypress.   
     720 *  
     721 *  If the browser does not report these as a keypress 
     722 *   ( https://dvcs.w3.org/hg/d4e/raw-file/tip/key-event-test.html ) 
     723 *   then this function must return true for such keydown events as it is 
     724 *   given. 
     725 *  
     726 * @param KeyboardEvent with keyEvent.type == keydown 
     727 * @return boolean 
     728 */ 
     729 
     730Xinha.prototype.isKeyDownThatShouldGetButDoesNotGetAKeyPressEvent = function(keyEvent) 
     731{ 
     732  // Dom 3 
     733  if(typeof keyEvent.key != 'undefined') 
     734  { 
     735    // Found using IE11 (which uses Gecko) 
     736    //   this seems to be a reasonable way to distinguish 
     737    //   between IE11 and other Gecko browsers which do  
     738    //   not provide the "Old DOM3" .char property 
     739    if(typeof keyEvent.char != 'undefined') 
     740    { 
     741      if(keyEvent.key.match(/^(Tab|Backspace|Del)/)) 
     742      { 
     743        return true; 
     744      } 
     745    } 
     746     
     747    // Firefox reports everything we need as a keypress 
     748    // correctly (in terms of Xinha) 
     749  } 
     750  // Legacy 
     751  else 
     752  { 
     753    // Even very old firefox reports everything we need as a keypress 
     754    // correctly (in terms of Xinha) 
     755  } 
     756}; 
    705757 
    706758/** Return the character (as a string) of a keyEvent  - ie, press the 'a' key and 
  • trunk/modules/WebKit/WebKit.js

    r1378 r1394  
    5757  // Handle shortcuts 
    5858  if(editor.isShortCut(ev)) 
    59   { 
     59  {     
    6060    switch(editor.getKey(ev).toLowerCase()) 
    6161    { 
     
    115115        }; 
    116116        editor._unlinkOnUndo = true; 
    117      
    118117        return a; 
    119118      }; 
     
    142141 
    143142          autoWrap(midTextEmail, 'a').href = 'mailto:' + mEmail[0]; 
    144           break; 
     143          return true; 
    145144        } 
    146145 
     
    162161          var midTextUrl   = leftTextUrl.splitText(midStart); 
    163162          autoWrap(midTextUrl, 'a').href = (mUrl[1] ? mUrl[1] : 'http://') + mUrl[2]; 
    164           break; 
     163          return true; 
    165164        } 
    166165      } 
     
    718717}; 
    719718 
    720 /** Determine if the given event object is a keydown/press event. 
     719 
     720/** Due to browser differences, some keys which Xinha prefers to call a keyPress 
     721 *   do not get an actual keypress event.  This browser specific function  
     722 *   overridden in the browser's engine (eg modules/WebKit/WebKit.js) as required 
     723 *   takes a keydown event type and tells us if we should treat it as a  
     724 *   keypress event type. 
    721725 * 
    722  *  @param event Event  
    723  *  @returns true|false 
    724  */ 
    725   
    726 Xinha.prototype.isKeyEvent = function(event) 
    727 { 
    728   return event.type == "keydown"; 
    729 } 
     726 *  To be clear, the keys we are interested here are 
     727 *        Escape, Tab, Backspace, Delete, Enter 
     728 *   these are "non printable" characters which we still want to regard generally 
     729 *   as a keypress.   
     730 *  
     731 *  If the browser does not report these as a keypress 
     732 *   ( https://dvcs.w3.org/hg/d4e/raw-file/tip/key-event-test.html ) 
     733 *   then this function must return true for such keydown events as it is 
     734 *   given. 
     735 *  
     736 * @param KeyboardEvent with keyEvent.type == keydown 
     737 * @return boolean 
     738 */ 
     739 
     740Xinha.prototype.isKeyDownThatShouldGetButDoesNotGetAKeyPressEvent = function(keyEvent) 
     741{ 
     742  // Dom 3 
     743  if(typeof keyEvent.key != 'undefined') 
     744  { 
     745    if(typeof Xinha.DOM3_WebKit_KeyDownKeyPress_RE == 'undefined') 
     746    { 
     747      // I don't know if pre-defining this is really faster in the modern world of 
     748      //  Javascript JIT compiling, but it does no harm 
     749      Xinha.DOM3_WebKit_KeyDownKeyPress_RE = /^(Escape|Esc|Tab|Backspace|Delete|Del)/; 
     750    } 
     751     
     752    // Found using Chrome 65 and Opera 50, Edge 
     753    //  Note that Edge calls Escape Esc, and Delete Del 
     754    if(Xinha.DOM3_WebKit_KeyDownKeyPress_RE.test(keyEvent.key)) 
     755    { 
     756      return true; 
     757    } 
     758  } 
     759  // Old Safari and Chrome 
     760  else if(typeof keyEvent.keyIdentifier != 'undefined' && keyEvent.keyIdentifier.length) 
     761  { 
     762    var kI = parseInt(keyEvent.keyIdentifier.replace(/^U\+/,''),16); 
     763    // Found using Safari 9.1.1 - safari seems to pass ESC ok as a keyPress 
     764    if(kI == 9 /* Tab */ || kI == 8 /* Backspace */ || kI == 127 /* Delete */ ) return true; 
     765  } 
     766  // Legacy 
     767  else 
     768  { 
     769    // Also Chrome 65, I'm assuming perhaps dangerously, that it's about the same as  
     770    // older pre-KeyboardEvent.key but that's been around a few years now so 
     771    // people will mostly be on supporting browsers anyway so this probably 
     772    // won't be hit by that many people 
     773    if(keyEvent.charCode == 0) 
     774    { 
     775      if( keyEvent.keyCode == 27  // ESC 
     776       || keyEvent.keyCode == 9   // Tab 
     777       || keyEvent.keyCode == 8   // Backspace 
     778       || keyEvent.keyCode == 46  // Del 
     779      ) return true; 
     780    } 
     781  } 
     782}; 
    730783 
    731784/** Return the character (as a string) of a keyEvent  - ie, press the 'a' key and 
     
    738791Xinha.prototype.getKey = function(keyEvent) 
    739792{  
    740  // with ctrl pressed Safari does not give the charCode, unfortunately this (shortcuts) is about the only thing this function is for 
    741   if(typeof keyEvent.keyIdentifier == 'undefined') 
    742   { 
    743     if(typeof keyEvent.key != 'undefined') 
    744     { 
    745       return keyEvent.key; 
    746     } 
    747   } 
    748    
    749   var key = String.fromCharCode(parseInt(keyEvent.keyIdentifier.replace(/^U\+/,''),16)); 
    750   if (keyEvent.shiftKey) return key; 
    751   else return key.toLowerCase(); 
     793  // DOM3 Key is easy (ish) 
     794  if(typeof keyEvent.key != 'undefined' && keyEvent.key.length > 0) 
     795  { 
     796    return keyEvent.key; 
     797  } 
     798  // Old DOM3 used by (Old?) Safari SOMETIMES (but not ALWAYS!) and old Chrome 
     799  else if(typeof keyEvent.keyIdentifier != 'undefined' && keyEvent.keyIdentifier.length > 0 && keyEvent.keyIdentifier.match(/^U\+/)) 
     800  { 
     801      var key = String.fromCharCode(parseInt(keyEvent.keyIdentifier.replace(/^U\+/,''),16)); 
     802      if (keyEvent.shiftKey) return key; 
     803      else return key.toLowerCase(); 
     804  } 
     805  // If charCode is specified, that's what we want 
     806  else if(keyEvent.charCode) 
     807  { 
     808    return String.fromCharCode(keyEvent.charCode); 
     809  } 
     810  // Safari does not set charCode if CTRL is pressed 
     811  //  but does set keyCode to the key, it also sets keyCode 
     812  //  for the actual pressing of ctrl, skip that 
     813  //  the keyCode in Safari si the uppercase character's code  
     814  //  for that key, so if shift is not pressed, lowercase it 
     815  else if(keyEvent.ctrlKey && keyEvent.keyCode != 17) 
     816  { 
     817    if(keyEvent.shiftKey) 
     818    { 
     819      return String.fromCharCode(keyEvent.keyCode); 
     820    } 
     821    else 
     822    { 
     823      return String.fromCharCode(keyEvent.keyCode).toLowerCase(); 
     824    } 
     825  } 
     826   
     827  // Ok, give up, no idea! 
     828  return ''; 
    752829} 
    753830 
Note: See TracChangeset for help on using the changeset viewer.