/* * Copyright(c) 2011 * OJC Technologies * all rights reserved * @author: Jake Smith */ /** * conceptual wrapper for and implementation of the cursor. */ function Cursor() { this.elemType = "span"; this.leftID = "leftcursorelement"; this.rightID = "rightcursorelement"; this.atEndOfLine = false; this.atStartOfLine = false; this.parentNode = null; this.LEFT = null; this.RIGHT = null; this.floater = null; this.isPlaced = function() { return (this.parentNode != null); } this.init = function() { this.LEFT = new SubCursor(); this.LEFT.init(this.elemType, this.leftID); this.RIGHT = new SubCursor(); this.RIGHT.init(this.elemType, this.rightID); } this.placeAt = function(textNode, offsetIndex) { var nothingPreceding, nothingFollowing, textLength; textLength = textNode.nodeValue.length; this.parentNode = textNode.parentNode; nothingPreceding = (offsetIndex == 0) ? true : false; nothingFollowing = (offsetIndex == textLength) ? true : false; if (nothingPreceding && nothingFollowing) { throw new Error("dropping cursor into empty thing!"); //this.addCursorOnly(textNode); } else if (nothingPreceding) { this.addCursorWithNoPrefix(textNode); } else if (nothingFollowing) { this.addCursorWithNoSuffix(textNode); } else { this.insertCursor(textNode, offsetIndex); } } this.shiftLeft = function() { var followingSib, tempNode; if (this.atStartOfLine) { return; } if (this.atEndOfLine) { this.RIGHT.pop(); this.atEndOfLine = false; } else { tempNode = this.RIGHT.pop(); followingSib = this.RIGHT.element.nextSibling; this.parentNode.insertBefore(tempNode, followingSib); this.parentNode.normalize(); } this.RIGHT.push(this.LEFT.pop()); this.leftSideGoesLeft(); this.parentNode.normalize(); } this.shiftRight = function() { var tempNode; if (this.atEndOfLine) { return; } if (this.atStartOfLine) { this.LEFT.pop(); this.atStartOfLine = false; } else { tempNode = this.LEFT.pop(); this.parentNode.insertBefore(tempNode, this.LEFT.element); } this.LEFT.push(this.RIGHT.pop()); this.rightSideGoesRight(); this.parentNode.normalize(); } this.remove = function() { var parent, rightContents, leftContents; if (!this.isPlaced()) { // unnecessary optimization? return; } parent = this.parentNode; //for legibility //get contents if (this.atEndOfLine) { this.RIGHT.pop(); leftContents = this.LEFT.pop(); } else if (this.atStartOfLine) { rightContents = this.RIGHT.pop(); this.LEFT.pop(); } else { rightContents = this.RIGHT.pop(); leftContents = this.LEFT.pop(); } //manage purview of left cursor element if (leftContents == null) { parent.removeChild(this.LEFT.element); } else { parent.replaceChild(leftContents, this.LEFT.element); } //manage purview of right cursor element if (rightContents == null) { parent.removeChild(this.RIGHT.element); } else { parent.replaceChild(rightContents, this.RIGHT.element); } //tidy up parent.normalize(); this.reset(); } this.doDelete = function() { if (!this.atEndOfLine) { this.RIGHT.pop(); this.rightSideGoesRight(); } } this.doBackspace = function() { if (!this.atStartOfLine) { this.LEFT.pop(); this.leftSideGoesLeft(); } } this.insertCharacter = function(c) { var tempNode; this.resetFloater(); tempNode = this.LEFT.pop(); if (!this.atStartOfLine) { this.parentNode.insertBefore(tempNode, this.LEFT.element); } tempNode = document.createTextNode(c); this.LEFT.push(tempNode); this.parentNode.normalize(); this.atStartOfLine = false; this.checkAndAddFloater(); } // internal this.reset = function() { this.atEndOfLine = false; this.atStartOfLine = false; this.parentNode = null; this.LEFT.pop(); this.RIGHT.pop(); this.resetFloater(); } // internal this.leftSideGoesLeft = function() { var prevSib, tempNode, tempText, selectIndex, selStr; prevSib = this.LEFT.element.previousSibling; this.resetFloater(); if (prevSib == null || prevSib.nodeValue == "" || prevSib.nodeType != Node.TEXT_NODE) { this.atStartOfLine = true; this.LEFT.push(document.createTextNode("\u00a0")); } else { tempText = prevSib.nodeValue; selectIndex = tempText.length - 1; if (!this.isToPreserveWhitespace(this.parentNode)) { selectIndex = getStartOffset(tempText, selectIndex); } selStr = tempText.substring(selectIndex); tempText = tempText.substring(0, selectIndex); tempNode = document.createTextNode(tempText); this.parentNode.replaceChild(tempNode, prevSib); tempNode = document.createTextNode(selStr); this.LEFT.push(tempNode); } this.checkAndAddFloater(); } // internal this.rightSideGoesRight = function() { var nextSib, tempNode, selectorEnd, tempText, selText; this.resetFloater(); nextSib = this.RIGHT.element.nextSibling; if (nextSib == null || nextSib.nodeValue == "" || nextSib.nodeType != Node.TEXT_NODE) { this.atEndOfLine = true; tempNode = document.createTextNode("_"); } else { tempText = nextSib.nodeValue; if (this.isToPreserveWhitespace(this.parentNode)) { selectorEnd = 1; } else { selectorEnd = getEndOffset(tempText, 0); } selText = tempText.substring(0, selectorEnd); tempText = tempText.substring(selectorEnd); tempNode = document.createTextNode(tempText); this.parentNode.replaceChild(tempNode, nextSib); tempNode = document.createTextNode(selText); } this.RIGHT.push(tempNode); this.checkAndAddFloater(); } // internal this.addCursorOnly = function(textNode) { } // internal this.addCursorWithNoPrefix = function(textNode) { var text, suffix, character, startOffset, endOffset; text = textNode.nodeValue; startOffset = 0; if (this.isToPreserveWhitespace(this.parentNode)) { endOffset = 1; } else { endOffset = getEndOffset(text, startOffset); } character = text.substring(startOffset, endOffset); character = document.createTextNode(character); this.RIGHT.push(character); this.LEFT.push(document.createTextNode("\u00a0")); text = text.substring(endOffset); suffix = document.createTextNode(text); this.parentNode.replaceChild(suffix, textNode); this.parentNode.insertBefore(this.LEFT.element, suffix); this.parentNode.insertBefore(this.RIGHT.element, suffix); this.atStartOfLine = true; } // internal this.addCursorWithNoSuffix = function(textNode) { var text, foreNode, character, foreString, offsetIndex; text = textNode.nodeValue; offsetIndex = text.length - 1; if (!this.isToPreserveWhitespace(this.parentNode)) { offsetIndex = getStartOffset(text, offsetIndex); } foreString = text.substring(0, offsetIndex); foreNode = document.createTextNode(foreString); character = text.substring(offsetIndex); character = document.createTextNode(character); this.RIGHT.push(document.createTextNode("_")); this.LEFT.push(character); this.parentNode.replaceChild(this.RIGHT.element, textNode); this.parentNode.insertBefore(this.LEFT.element, this.RIGHT.element); this.parentNode.insertBefore(foreNode, this.LEFT.element); this.atEndOfLine = true; } // internal this.insertCursor = function(textNode, offsetIndex) { var text, prefix, suffix, lrange, rrange, startIndex, endIndex, valueWhitespace; text = textNode.nodeValue; valueWhitespace = this.isToPreserveWhitespace(this.parentNode); if (valueWhitespace) { startIndex = offsetIndex; endIndex = startIndex + 1; } else { endIndex = getEndOffset(text, offsetIndex); startIndex = getStartOffset(text, offsetIndex); } rrange = text.substring(startIndex, endIndex); suffix = text.substring(endIndex); endIndex = startIndex; startIndex = startIndex - 1; if (!valueWhitespace) { startIndex = getStartOffset(text, startIndex); } lrange = text.substring(startIndex, endIndex); prefix = text.substring(0, startIndex); prefix = document.createTextNode(prefix); lrange = document.createTextNode(lrange); rrange = document.createTextNode(rrange); suffix = document.createTextNode(suffix); this.LEFT.push(lrange); this.RIGHT.push(rrange); this.parentNode.replaceChild(suffix, textNode); this.parentNode.insertBefore(this.RIGHT.element, suffix); this.parentNode.insertBefore(this.LEFT.element, this.RIGHT.element); this.parentNode.insertBefore(prefix, this.LEFT.element); } //internal this.isToPreserveWhitespace = function(givenNode) { var tag, nodeParent; tag = givenNode.tagName; if (tag == "BODY") { return false; } else if (tag == "PRE") { return true; } else { nodeParent = givenNode.parentNode; return this.isToPreserveWhitespace(nodeParent); } } //internal this.resetFloater = function() { if (this.floater == null) { return; } else { this.floater.remove(); this.floater = null; } } //internal this.checkAndAddFloater = function() { if (this.floater != null) { throw new Error("You have a standing FloatingCursor element "+ "in the cursor. you must call Cursor.resetFloater() before"+ " calling Cursor.checkAndAddFloater()"); } if (isCollapsed(this.RIGHT.element)) { this.floater = new FloatingCursor(); this.floater.addToPageBehind(this.LEFT.element); } if (isCollapsed(this.LEFT.element)) { this.floater = new FloatingCursor(); this.floater.addToPageOver(this.LEFT.element); } } } function isCollapsed(cursorSpanElem) { var rect; rect = cursorSpanElem.getBoundingClientRect(); return rect.width == 0; } function getEndOffset(text, startI) { var endI, testChar; endI = startI + 1; testChar = text.substring(startI, endI); while (true) { if (isWhitespace(testChar)) { endI = startI + 1; startI = endI; testChar = text.substring(startI, startI + 1); } else { break; } } return endI; } function getStartOffset(text, startI) { var endI, testChar; endI = startI + 1; testChar = text.substring(startI, endI); while (true) { if (isWhitespace(testChar)) { startI = endI - 1; endI = startI; testChar = text.substring(endI - 1, endI); } else { break; } } return startI; } function isWhitespace(character) { var pattern = /\s/; if (character.match(pattern)) { return true; } return false; }