diff --git a/Makefile b/Makefile index 5bb70d8..d701ac8 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ clean: build: build/squire.js build/document.html -build/squire.js: source/Node.js source/Range.js source/Editor.js +build/squire.js: source/TreeWalker.js source/Node.js source/Range.js source/Editor.js mkdir -p $(@D) cat $^ | uglifyjs > $@ diff --git a/source/Node.js b/source/Node.js index afc19f1..c92a425 100644 --- a/source/Node.js +++ b/source/Node.js @@ -50,53 +50,9 @@ var ELEMENT_NODE = 1, // Node.ELEMENT_NODE, FILTER_ACCEPT = 1, // NodeFilter.FILTER_ACCEPT, FILTER_SKIP = 3; // NodeFilter.FILTER_SKIP; -var walkForward = function ( current, filter ) { - var node; - while ( true ) { - node = current.firstChild; - while ( !node && current ) { - node = current.nextSibling; - if ( !node ) { - current = current.parentNode; - } - } - - if ( !node ) { - return null; - } - if ( filter( node ) ) { - return node; - } - - current = node; - } +var isBlock = function ( el ) { + return el.isBlock() ? FILTER_ACCEPT : FILTER_SKIP; }; - -var walkBackward = function ( current, filter ) { - var node; - while ( true ) { - node = current.previousSibling; - if ( node ) { - while ( current = node.lastChild ) { - node = current; - } - } else { - node = current.parentNode; - } - - if ( node.nodeName === 'BODY' || - node.nodeType === DOCUMENT_FRAGMENT_NODE ) { - return null; - } - if ( filter( node ) ) { - return node; - } - - current = node; - } -}; - -var isBlock = function ( el ) { return el.isBlock(); }; var useTextFixer = !!( window.opera || window.ie ); implement( Node, { @@ -127,10 +83,18 @@ implement( Node, { return parent ? parent.nearest( tag, attributes ) : null; }, getPreviousBlock: function () { - return walkBackward( this, isBlock ); + var doc = this.ownerDocument, + walker = doc.createTreeWalker( + doc.body, SHOW_ELEMENT, isBlock, false ); + walker.currentNode = this; + return walker.previousNode(); }, getNextBlock: function () { - return walkForward( this, isBlock ); + var doc = this.ownerDocument, + walker = doc.createTreeWalker( + doc.body, SHOW_ELEMENT, isBlock, false ); + walker.currentNode = this; + return walker.nextNode(); }, split: function ( node, stopCondition ) { return node; diff --git a/source/TreeWalker.js b/source/TreeWalker.js new file mode 100644 index 0000000..20d8569 --- /dev/null +++ b/source/TreeWalker.js @@ -0,0 +1,133 @@ +/* Copyright © 2011 by Neil Jenkins. Licensed under the MIT license. */ + +( function ( doc ) { + +/*global document, window */ + +"use strict"; + +// --- + +var needsReplacement = !doc.createTreeWalker; + +// IE9 sometimes throws errors when calling TreeWalker#nextNode or +// TreeWalker#previousNode. No way to feature detect this. +if ( window.ie === 9 ) { + needsReplacement = true; +} + +// Feature detect Opera bug in TreeWalker#previousNode +if ( !needsReplacement ) { + ( function () { + var div = doc.createElement( 'div' ), + text = doc.createTextNode( '' ); + + div.appendChild( text ); + + var div1 = div.cloneNode( true ), + div2 = div.cloneNode( true ), + div3 = div.cloneNode( true ), + walker = doc.createTreeWalker( div, 1, function ( node ) { + return 1; + }, false ); + div.appendChild( div1 ); + div.appendChild( div2 ); + div.appendChild( div3 ); + walker.currentNode = div3; + if ( walker.previousNode() !== div2 ) { + needsReplacement = true; + } + }() ); +} + +if ( !needsReplacement ) { return; } + +// --- + +var typeToBitArray = { + // ELEMENT_NODE + 1: 1, + // ATTRIBUTE_NODE + 2: 2, + // TEXT_NODE + 3: 4, + // COMMENT_NODE + 8: 128, + // DOCUMENT_NODE + 9: 256, + // DOCUMENT_FRAGMENT_NODE + 11: 1024 +}; + +var FILTER_ACCEPT = 1; + +var TreeWalker = function ( root, nodeType, filter ) { + this.root = this.currentNode = root; + this.nodeType = nodeType; + this.filter = filter; +}; + +TreeWalker.prototype.nextNode = function () { + var current = this.currentNode, + root = this.root, + nodeType = this.nodeType, + filter = this.filter, + node; + while ( true ) { + node = current.firstChild; + while ( !node && current ) { + node = current.nextSibling; + if ( !node ) { + current = current.parentNode; + if ( current === root ) { + return null; + } + } + } + + if ( !node ) { + return null; + } + if ( ( typeToBitArray[ node.nodeType ] & nodeType ) && + filter( node ) === FILTER_ACCEPT ) { + this.currentNode = node; + return node; + } + + current = node; + } +}; + +TreeWalker.prototype.previousNode = function () { + var current = this.currentNode, + root = this.root, + nodeType = this.nodeType, + filter = this.filter, + node; + while ( true ) { + if ( node === root ) { + return null; + } + node = current.previousSibling; + if ( node ) { + while ( current = node.lastChild ) { + node = current; + } + } else { + node = current.parentNode; + } + if ( ( typeToBitArray[ node.nodeType ] & nodeType ) && + filter( node ) === FILTER_ACCEPT ) { + this.currentNode = node; + return node; + } + + current = node; + } +}; + +doc.createTreeWalker = function ( root, nodeType, filter ) { + return new TreeWalker( root, nodeType, filter ); +}; + +}( document ) ); \ No newline at end of file