dijit/_editor/plugins/EnterKeyHandling.js

  • Provides:

    • dijit._editor.plugins.EnterKeyHandling
  • dijit._editor.plugins.EnterKeyHandling

    • type
      Function
    • chains:
      • dijit._editor._Plugin: (prototype)
      • dijit._editor._Plugin: (call)
    • summary
      This plugin tries to make all browsers behave consistently with regard to
      how ENTER behaves in the editor window.  It traps the ENTER key and alters
      the way DOM is constructed in certain cases to try to commonize the generated
      DOM and behaviors across browsers.
    • description
      This plugin has three modes:
      
      * blockModeForEnter=BR
      * blockModeForEnter=DIV
      * blockModeForEnter=P
      
      In blockModeForEnter=P, the ENTER key starts a new
      paragraph, and shift-ENTER starts a new line in the current paragraph.
      For example, the input:
      
      	first paragraph <shift-ENTER>
      	second line of first paragraph <ENTER>
      	second paragraph
      
      will generate:
      
      	<p>
      		first paragraph
      		<br/>
      		second line of first paragraph
      	</p>
      	<p>
      		second paragraph
      	</p>
      
      In BR and DIV mode, the ENTER key conceptually goes to a new line in the
      current paragraph, and users conceptually create a new paragraph by pressing ENTER twice.
      For example, if the user enters text into an editor like this:
      
      		one <ENTER>
      		two <ENTER>
      		three <ENTER>
      		<ENTER>
      		four <ENTER>
      		five <ENTER>
      		six <ENTER>
      
      It will appear on the screen as two 'paragraphs' of three lines each.  Markupwise, this generates:
      
      BR:
      		one<br/>
      		two<br/>
      		three<br/>
      		<br/>
      		four<br/>
      		five<br/>
      		six<br/>
      
      DIV:
      		<div>one</div>
      		<div>two</div>
      		<div>three</div>
      		<div>&nbsp;</div>
      		<div>four</div>
      		<div>five</div>
      		<div>six</div>
    • parameters:
      • args: (typeof )
    • source: [view]
        if(args){
         if("blockNodeForEnter" in args){
          args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase();
         }
         dojo.mixin(this,args);
        }
  • dijit._editor.plugins.EnterKeyHandling.blockNodeForEnter

    • type
      String
    • summary
      This property decides the behavior of Enter key. It can be either P,
      DIV, BR, or empty (which means disable this feature). Anything else
      will trigger errors.  The default is 'BR'
      
      See class description for more details.
  • dijit._editor.plugins.EnterKeyHandling.setEditor

    • type
      Function
    • parameters:
      • editor: (typeof )
    • source: [view]
      define("dijit/_editor/plugins/EnterKeyHandling", ["dojo", "dijit", "dojo/window", "dijit/_editor/_Plugin", "dijit/_editor/range"], function(dojo, dijit) {


      dojo.declare("dijit._editor.plugins.EnterKeyHandling", dijit._editor._Plugin, {
       // summary:
       //  This plugin tries to make all browsers behave consistently with regard to
       //  how ENTER behaves in the editor window. It traps the ENTER key and alters
       //  the way DOM is constructed in certain cases to try to commonize the generated
       //  DOM and behaviors across browsers.
       //
       // description:
       //  This plugin has three modes:
       //
       //   * blockModeForEnter=BR
       //   * blockModeForEnter=DIV
       //   * blockModeForEnter=P
       //
       //  In blockModeForEnter=P, the ENTER key starts a new
       //  paragraph, and shift-ENTER starts a new line in the current paragraph.
       //  For example, the input:
       //
       //  | first paragraph
       //  | second line of first paragraph
       //  | second paragraph
       //
       //  will generate:
       //
       //  | 


       //  |  first paragraph
       //  |  

       //  |  second line of first paragraph
       //  | 


       //  | 


       //  |  second paragraph
       //  | 


       //
       //  In BR and DIV mode, the ENTER key conceptually goes to a new line in the
       //  current paragraph, and users conceptually create a new paragraph by pressing ENTER twice.
       //  For example, if the user enters text into an editor like this:
       //
       //  |  one
       //  |  two
       //  |  three
       //  |  
       //  |  four
       //  |  five
       //  |  six
       //
       //  It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates:
       //
       //  BR:
       //  |  one

       //  |  two

       //  |  three

       //  |  

       //  |  four

       //  |  five

       //  |  six

       //
       //  DIV:
       //  |  
      one

       //  |  
      two

       //  |  
      three

       //  |  
       

       //  |  
      four

       //  |  
      five

       //  |  
      six



       // blockNodeForEnter: String
       //  This property decides the behavior of Enter key. It can be either P,
       //  DIV, BR, or empty (which means disable this feature). Anything else
       //  will trigger errors. The default is 'BR'
       //
       //  See class description for more details.
       blockNodeForEnter: 'BR',


       constructor: function(args){
        if(args){
         if("blockNodeForEnter" in args){
          args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase();
         }
         dojo.mixin(this,args);
        }
       },


       setEditor: function(editor){
        // Overrides _Plugin.setEditor().
        if(this.editor === editor) { return; }
        this.editor = editor;
        if(this.blockNodeForEnter == 'BR'){
         // While Moz has a mode tht mostly works, it's still a little different,
         // So, try to just have a common mode and be consistent. Which means
         // we need to enable customUndo, if not already enabled.
         this.editor.customUndo = true;
          editor.onLoadDeferred.addCallback(dojo.hitch(this,function(d){
          this.connect(editor.document, "onkeypress", function(e){
           if(e.charOrCode == dojo.keys.ENTER){
            // Just do it manually. The handleEnterKey has a shift mode that
            // Always acts like
      , so just use it.
            var ne = dojo.mixin({},e);
            ne.shiftKey = true;
            if(!this.handleEnterKey(ne)){
             dojo.stopEvent(e);
            }
           }
          });
           return d;
          }));
        }else if(this.blockNodeForEnter){
         // add enter key handler
         // FIXME: need to port to the new event code!!
         var h = dojo.hitch(this,this.handleEnterKey);
         editor.addKeyHandler(13, 0, 0, h); //enter
         editor.addKeyHandler(13, 0, 1, h); //shift+enter
         this.connect(this.editor,'onKeyPressed','onKeyPressed');
        }
    • summary
  • dijit._editor.plugins.EnterKeyHandling.onKeyPressed

    • type
      Function
    • parameters:
      • e: (typeof )
    • source: [view]
        if(this._checkListLater){
         if(dojo.withGlobal(this.editor.window, 'isCollapsed', dijit)){
          var liparent=dojo.withGlobal(this.editor.window, 'getAncestorElement', dijit._editor.selection, ['LI']);
          if(!liparent){
           // circulate the undo detection code by calling RichText::execCommand directly
           dijit._editor.RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter);
           // set the innerHTML of the new block node
           var block = dojo.withGlobal(this.editor.window, 'getAncestorElement', dijit._editor.selection, [this.blockNodeForEnter]);
           if(block){
            block.innerHTML=this.bogusHtmlContent;
            if(dojo.isIE){
             // move to the start by moving backwards one char
             var r = this.editor.document.selection.createRange();
             r.move('character',-1);
             r.select();
            }
           }else{
            console.error('onKeyPressed: Cannot find the new block node'); // FIXME
           }
          }else{
           if(dojo.isMoz){
            if(liparent.parentNode.parentNode.nodeName == 'LI'){
             liparent=liparent.parentNode.parentNode;
            }
           }
           var fc=liparent.firstChild;
           if(fc && fc.nodeType == 1 && (fc.nodeName == 'UL' || fc.nodeName == 'OL')){
            liparent.insertBefore(fc.ownerDocument.createTextNode('\xA0'),fc);
            var newrange = dijit.range.create(this.editor.window);
            newrange.setStart(liparent.firstChild,0);
            var selection = dijit.range.getSelection(this.editor.window, true);
            selection.removeAllRanges();
            selection.addRange(newrange);
           }
          }
         }
         this._checkListLater = false;
        }
        if(this._pressedEnterInBlock){
         // the new created is the original current P, so we have previousSibling below
         if(this._pressedEnterInBlock.previousSibling){
          this.removeTrailingBr(this._pressedEnterInBlock.previousSibling);
         }
         delete this._pressedEnterInBlock;
        }
    • summary
      Handler for keypress events.
    • tags:
    • chains:
      • dijit._editor.RichText.prototype.execCommand: (call)
  • dijit._editor.plugins.EnterKeyHandling.bogusHtmlContent

    • tags: private
    • type
      String
    • summary
      HTML to stick into a new empty block
  • dijit._editor.plugins.EnterKeyHandling.blockNodes

    • tags: private
    • type
      Regex
    • summary
      Regex for testing if a given tag is a block level (display:block) tag
  • dijit._editor.plugins.EnterKeyHandling.handleEnterKey

    • type
      Function
    • parameters:
      • e: (typeof )
    • source: [view]
        var selection, range, newrange, startNode, endNode, brNode, doc=this.editor.document,br,rs,txt;
        if(e.shiftKey){  // shift+enter always generates

         var parent = dojo.withGlobal(this.editor.window, "getParentElement", dijit._editor.selection);
         var header = dijit.range.getAncestor(parent,this.blockNodes);
         if(header){
          if(header.tagName == 'LI'){
           return true; // let browser handle
          }
          selection = dijit.range.getSelection(this.editor.window);
          range = selection.getRangeAt(0);
          if(!range.collapsed){
           range.deleteContents();
           selection = dijit.range.getSelection(this.editor.window);
           range = selection.getRangeAt(0);
          }
          if(dijit.range.atBeginningOfContainer(header, range.startContainer, range.startOffset)){
            br=doc.createElement('br');
            newrange = dijit.range.create(this.editor.window);
            header.insertBefore(br,header.firstChild);
            newrange.setStartBefore(br.nextSibling);
            selection.removeAllRanges();
            selection.addRange(newrange);
          }else if(dijit.range.atEndOfContainer(header, range.startContainer, range.startOffset)){
           newrange = dijit.range.create(this.editor.window);
           br=doc.createElement('br');
            header.appendChild(br);
            header.appendChild(doc.createTextNode('\xA0'));
            newrange.setStart(header.lastChild,0);
           selection.removeAllRanges();
           selection.addRange(newrange);
          }else{
           rs = range.startContainer;
           if(rs && rs.nodeType == 3){
            // Text node, we have to split it.
            txt = rs.nodeValue;
            dojo.withGlobal(this.editor.window, function(){
             startNode = doc.createTextNode(txt.substring(0, range.startOffset));
             endNode = doc.createTextNode(txt.substring(range.startOffset));
             brNode = doc.createElement("br");

             
             if(endNode.nodeValue == "" && dojo.isWebKit){
              endNode = doc.createTextNode('\xA0')
             }
             dojo.place(startNode, rs, "after");
             dojo.place(brNode, startNode, "after");
             dojo.place(endNode, brNode, "after");
             dojo.destroy(rs);
             newrange = dijit.range.create(dojo.gobal);
             newrange.setStart(endNode,0);
             selection.removeAllRanges();
             selection.addRange(newrange);
            });
            return false;
           }
           return true; // let browser handle
          }
         }else{
          selection = dijit.range.getSelection(this.editor.window);
          if(selection.rangeCount){
           range = selection.getRangeAt(0);
           if(range && range.startContainer){
            if(!range.collapsed){
             range.deleteContents();
             selection = dijit.range.getSelection(this.editor.window);
             range = selection.getRangeAt(0);
            }
            rs = range.startContainer;
            if(rs && rs.nodeType == 3){
             // Text node, we have to split it.
             dojo.withGlobal(this.editor.window, dojo.hitch(this, function(){
              var endEmpty = false;

             
              var offset = range.startOffset;
              if(rs.length < offset){
               //We are not splitting the right node, try to locate the correct one
               ret = this._adjustNodeAndOffset(rs, offset);
               rs = ret.node;
               offset = ret.offset;
              }
              txt = rs.nodeValue;

          
              startNode = doc.createTextNode(txt.substring(0, offset));
              endNode = doc.createTextNode(txt.substring(offset));
              brNode = doc.createElement("br");

              
              if(!endNode.length){
               endNode = doc.createTextNode('\xA0');
               endEmpty = true;
              }

              
              if(startNode.length){
               dojo.place(startNode, rs, "after");
              }else{
               startNode = rs;
              }
              dojo.place(brNode, startNode, "after");
              dojo.place(endNode, brNode, "after");
              dojo.destroy(rs);
              newrange = dijit.range.create(dojo.gobal);
              newrange.setStart(endNode,0);
              newrange.setEnd(endNode, endNode.length);
              selection.removeAllRanges();
              selection.addRange(newrange);
              if(endEmpty && !dojo.isWebKit){
               dijit._editor.selection.remove();
              }else{
               dijit._editor.selection.collapse(true);
              }
             }));
            }else{
             dojo.withGlobal(this.editor.window, dojo.hitch(this, function(){
              var brNode = doc.createElement("br");
              rs.appendChild(brNode);
              var endNode = doc.createTextNode('\xA0');
              rs.appendChild(endNode);
              newrange = dijit.range.create(dojo.global);
              newrange.setStart(endNode,0);
              newrange.setEnd(endNode, endNode.length);
              selection.removeAllRanges();
              selection.addRange(newrange);
              dijit._editor.selection.collapse(true);
             }));
            }
           }
          }else{
           // don't change this: do not call this.execCommand, as that may have other logic in subclass
           dijit._editor.RichText.prototype.execCommand.call(this.editor, 'inserthtml', '
      ');
          }
         }
         return false;
        }
        var _letBrowserHandle = true;


        // first remove selection
        selection = dijit.range.getSelection(this.editor.window);
        range = selection.getRangeAt(0);
        if(!range.collapsed){
         range.deleteContents();
         selection = dijit.range.getSelection(this.editor.window);
         range = selection.getRangeAt(0);
        }


        var block = dijit.range.getBlockAncestor(range.endContainer, null, this.editor.editNode);
        var blockNode = block.blockNode;


        // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it
        if((this._checkListLater = (blockNode && (blockNode.nodeName == 'LI' || blockNode.parentNode.nodeName == 'LI')))){
         if(dojo.isMoz){
          // press enter in middle of P may leave a trailing
      , let's remove it later
          this._pressedEnterInBlock = blockNode;
         }
         // if this li only contains spaces, set the content to empty so the browser will outdent this item
         if(/^(\s| |\xA0|]*\bclass=['"]Apple-style-span['"][^>]*>(\s| |\xA0)<\/span>)?(
      )?$/.test(blockNode.innerHTML)){
          // empty LI node
          blockNode.innerHTML = '';
          if(dojo.isWebKit){ // WebKit tosses the range when innerHTML is reset
           newrange = dijit.range.create(this.editor.window);
           newrange.setStart(blockNode, 0);
           selection.removeAllRanges();
           selection.addRange(newrange);
          }
          this._checkListLater = false; // nothing to check since the browser handles outdent
         }
         return true;
        }


        // text node directly under body, let's wrap them in a node
        if(!block.blockNode || block.blockNode===this.editor.editNode){
         try{
          dijit._editor.RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter);
         }catch(e2){ /*squelch FF3 exception bug when editor content is a single BR*/ }
         // get the newly created block node
         // FIXME
         block = {blockNode:dojo.withGlobal(this.editor.window, "getAncestorElement", dijit._editor.selection, [this.blockNodeForEnter]),
           blockContainer: this.editor.editNode};
         if(block.blockNode){
          if(block.blockNode != this.editor.editNode &&
           (!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length)){
           this.removeTrailingBr(block.blockNode);
           return false;
          }
         }else{ // we shouldn't be here if formatblock worked
          block.blockNode = this.editor.editNode;
         }
         selection = dijit.range.getSelection(this.editor.window);
         range = selection.getRangeAt(0);
        }


        var newblock = doc.createElement(this.blockNodeForEnter);
        newblock.innerHTML=this.bogusHtmlContent;
        this.removeTrailingBr(block.blockNode);
        var endOffset = range.endOffset;
        var node = range.endContainer;
        if(node.length < endOffset){
         //We are not checking the right node, try to locate the correct one
         var ret = this._adjustNodeAndOffset(node, endOffset);
         node = ret.node;
         endOffset = ret.offset;
        }
        if(dijit.range.atEndOfContainer(block.blockNode, node, endOffset)){
         if(block.blockNode === block.blockContainer){
          block.blockNode.appendChild(newblock);
         }else{
          dojo.place(newblock, block.blockNode, "after");
         }
         _letBrowserHandle = false;
         // lets move caret to the newly created block
         newrange = dijit.range.create(this.editor.window);
         newrange.setStart(newblock, 0);
         selection.removeAllRanges();
         selection.addRange(newrange);
         if(this.editor.height){
          dojo.window.scrollIntoView(newblock);
         }
        }else if(dijit.range.atBeginningOfContainer(block.blockNode,
          range.startContainer, range.startOffset)){
         dojo.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before");
         if(newblock.nextSibling && this.editor.height){
          // position input caret - mostly WebKit needs this
          newrange = dijit.range.create(this.editor.window);
          newrange.setStart(newblock.nextSibling, 0);
          selection.removeAllRanges();
          selection.addRange(newrange);
          // browser does not scroll the caret position into view, do it manually
          dojo.window.scrollIntoView(newblock.nextSibling);
         }
         _letBrowserHandle = false;
        }else{ //press enter in the middle of P/DIV/Whatever/
         if(block.blockNode === block.blockContainer){
          block.blockNode.appendChild(newblock);
         }else{
          dojo.place(newblock, block.blockNode, "after");
         }
         _letBrowserHandle = false;


         // Clone any block level styles.
         if(block.blockNode.style){
          if(newblock.style){
           if(block.blockNode.style.cssText){
            newblock.style.cssText = block.blockNode.style.cssText;
           }
          }
         }

         
         // Okay, we probably have to split.
         rs = range.startContainer;
         var firstNodeMoved;
         if(rs && rs.nodeType == 3){
          // Text node, we have to split it.
          var nodeToMove, tNode;
          endOffset = range.endOffset;
          if(rs.length < endOffset){
           //We are not splitting the right node, try to locate the correct one
           ret = this._adjustNodeAndOffset(rs, endOffset);
           rs = ret.node;
           endOffset = ret.offset;
          }

          
          txt = rs.nodeValue;
          startNode = doc.createTextNode(txt.substring(0, endOffset));
          endNode = doc.createTextNode(txt.substring(endOffset, txt.length));


          // Place the split, then remove original nodes.
          dojo.place(startNode, rs, "before");
          dojo.place(endNode, rs, "after");
          dojo.destroy(rs);


          // Okay, we split the text. Now we need to see if we're
          // parented to the block element we're splitting and if
          // not, we have to split all the way up. Ugh.
          var parentC = startNode.parentNode;
          while(parentC !== block.blockNode){
           var tg = parentC.tagName;
           var newTg = doc.createElement(tg);
           // Clone over any 'style' data.
           if(parentC.style){
            if(newTg.style){
             if(parentC.style.cssText){
              newTg.style.cssText = parentC.style.cssText;
             }
            }
           }
           // If font also need to clone over any font data.
           if(parentC.tagName === "FONT"){
            if(parentC.color){
             newTg.color = parentC.color;
            }
            if(parentC.face){
             newTg.face = parentC.face;
            }
            if(parentC.size){ // this check was necessary on IE
             newTg.size = parentC.size;
            }
           }

           
           nodeToMove = endNode;
           while(nodeToMove){
            tNode = nodeToMove.nextSibling;
            newTg.appendChild(nodeToMove);
            nodeToMove = tNode;
           }
           dojo.place(newTg, parentC, "after");
           startNode = parentC;
           endNode = newTg;
           parentC = parentC.parentNode;
          }


          // Lastly, move the split out tags to the new block.
          // as they should now be split properly.
          nodeToMove = endNode;
          if(nodeToMove.nodeType == 1 || (nodeToMove.nodeType == 3 && nodeToMove.nodeValue)){
           // Non-blank text and non-text nodes need to clear out that blank space
           // before moving the contents.
           newblock.innerHTML = "";
          }
          firstNodeMoved = nodeToMove;
          while(nodeToMove){
           tNode = nodeToMove.nextSibling;
           newblock.appendChild(nodeToMove);
           nodeToMove = tNode;
          }
         }

         
         //lets move caret to the newly created block
         newrange = dijit.range.create(this.editor.window);
         var nodeForCursor;
         var innerMostFirstNodeMoved = firstNodeMoved;
         if(this.blockNodeForEnter !== 'BR'){
          while(innerMostFirstNodeMoved){
           nodeForCursor = innerMostFirstNodeMoved;
           tNode = innerMostFirstNodeMoved.firstChild;
           innerMostFirstNodeMoved = tNode;
          }
          if(nodeForCursor && nodeForCursor.parentNode){
           newblock = nodeForCursor.parentNode;
           newrange.setStart(newblock, 0);
           selection.removeAllRanges();
           selection.addRange(newrange);
           if(this.editor.height){
            dijit.scrollIntoView(newblock);
           }
           if(dojo.isMoz){
            // press enter in middle of P may leave a trailing
      , let's remove it later
            this._pressedEnterInBlock = block.blockNode;
           }     
          }else{
           _letBrowserHandle = true;
          }
         }else{
          newrange.setStart(newblock, 0);
          selection.removeAllRanges();
          selection.addRange(newrange);
          if(this.editor.height){
           dijit.scrollIntoView(newblock);
          }
          if(dojo.isMoz){
           // press enter in middle of P may leave a trailing
      , let's remove it later
           this._pressedEnterInBlock = block.blockNode;
          }
         }
        }
        return _letBrowserHandle;
    • summary
      Handler for enter key events when blockModeForEnter is DIV or P.
    • description
      Manually handle enter key event to make the behavior consistent across
      all supported browsers. See class description for details.
    • tags:
    • returns
      let browser handle
    • chains:
      • dijit._editor.RichText.prototype.execCommand: (call)
  • dijit._editor.plugins.EnterKeyHandling._adjustNodeAndOffset

    • type
      Function
    • parameters:
      • node: (typeof DomNode)
        The node to check.
      • offset: (typeof Int)
        The position to find within the text node
    • source: [view]
        while(node.length < offset && node.nextSibling && node.nextSibling.nodeType==3){
         //Adjust the offset and node in the case of multiple text nodes in a row
         offset = offset - node.length;
         node = node.nextSibling;
        }
        var ret = {"node": node, "offset": offset};
        return ret;
    • summary
      In the case there are multiple text nodes in a row the offset may not be within the node.  If the offset is larger than the node length, it will attempt to find
      the next text sibling until it locates the text node in which the offset refers to
    • tags:
  • dijit._editor.plugins.EnterKeyHandling.removeTrailingBr

    • type
      Function
    • parameters:
      • container: (typeof )
    • source: [view]
        var para = /P|DIV|LI/i.test(container.tagName) ?
         container : dijit._editor.selection.getParentOfType(container,['P','DIV','LI']);


        if(!para){ return; }
        if(para.lastChild){
         if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) ||
          para.lastChild.tagName=='BR'){


          dojo.destroy(para.lastChild);
         }
        }
        if(!para.childNodes.length){
         para.innerHTML=this.bogusHtmlContent;
        }
    • summary
      If last child of container is a &lt;br&gt;, then remove it.
    • tags:
  • dijit._editor.plugins.EnterKeyHandling.editor

    • summary
  • dijit._editor.plugins.EnterKeyHandling.editor.customUndo

    • summary
  • dijit._editor.plugins.EnterKeyHandling._checkListLater

    • summary
  • dijit._editor.plugins.EnterKeyHandling._pressedEnterInBlock

    • summary
  • this

    • mixins:
      • args: (normal)
    • summary
  • dijit._editor.plugins

    • type
      Object
    • summary
  • dijit._editor

    • type
      Object
    • summary
  • dijit

    • type
      Object
    • summary