/* * Copyright(c) 2011 * OJC Technologies * all rights reserved * @author: Jake Smith */ function takeClickAction(e) { try { e.preventDefault(); processClick(e); } catch (err) { alert(err); } return false; } function processClick(e) { var event, target; event = e || window.event; event.stopPropagation(); target = event.target || event.srcElement; if (target == cursor.LEFT.element || target == cursor.RIGHT.element) { return; } cursor.remove(); if (target.nodeName == 'BODY' || target.nodeName == 'HTML') { return; // for now? } placeACursor(event, target); } function placeACursor(event, target) { var clickChooser, contenderNodes, posInfo; clickChooser = new ClickChooser(event); contenderNodes = getTerminalNodesIn(target); posInfo = clickChooser.getPositionInfo(contenderNodes); cursor.placeAt(posInfo[0], posInfo[1]); } // standalone 'helper' function function getTerminalNodesIn(target) { var i, termNodes, termCount; termNodes = new Array(); termCount = 0; for (i = 0; i < target.childNodes.length; i++) { var childNode = target.childNodes[i]; if (childNode.nodeType == Node.TEXT_NODE) { termNodes[termCount] = childNode; termCount++; } } return termNodes; } function ClickChooser(event) { this.x = event.pageX; this.y = event.pageY; this.getPositionInfo = function(terminalNodes) { var clickedTextNode, helper, posInfo, foundIndex; clickedTextNode = this.getClickedTextNode(terminalNodes); if (clickedTextNode != null) { foundIndex = this.specifyIndex(clickedTextNode); posInfo = new Array(clickedTextNode, foundIndex); } else { helper = new ProximityHelper(event); posInfo = helper.getInfoFromRelation(terminalNodes); } return posInfo; } this.getClickedTextNode = function(terminalNodes) { var i, consideredNode; for (i = 0; i < terminalNodes.length; i += 1) { consideredNode = terminalNodes[i]; if (this.clickInBounds(consideredNode)) { return consideredNode; } } return null; // not in any of the terminal nodes } this.clickInBounds = function(textNode) { var i, rects, tempSpan, flag, parent; parent = textNode.parentNode; flag = false; tempSpan = makeTempSpan(textNode); parent.replaceChild(tempSpan, textNode); rects = tempSpan.getClientRects(); for (i = 0; i < rects.length; i += 1) { if (rectContains(rects[i], this.x, this.y)) { flag = true; } } parent.replaceChild(textNode, tempSpan); return flag; } this.specifyIndex = function(node) { var textLength, index, NOT_HERE; NOT_HERE = -2; textLength = node.nodeValue.length; if (textLength < 1) { index = NOT_HERE; // definitely not here } else if (textLength === 1) { index = (this.clickInBounds(node)) ? 0 : NOT_HERE; } else { // length > 1 index = this.splitAndSearch(node, textLength); } return index; } this.splitAndSearch = function(node, textLength) { var fence, index, parent; parent = node.parentNode fence = Math.ceil(textLength / 2); var newNode = node.splitText(fence); if (this.clickInBounds(node)) { index = this.specifyIndex(node); } else if (this.clickInBounds(newNode)) { index = fence + this.specifyIndex(newNode); } else { throw new Error("error: specifyIndex search"); } parent.normalize(); return index; } } function ProximityHelper(event) { this.x = event.pageX; this.y = event.pageY; this.getInfoFromRelation = function(terminalNodes) { var properNode, index, firstNode, lastNode; firstNode = terminalNodes[0]; lastNode = terminalNodes[terminalNodes.length - 1]; if (this.clickedAbove(firstNode) || clickedToLeft(this.x, this.y, terminalNodes)) { properNode = firstNode; index = 0; } else if (this.clickedBelow(lastNode)) { properNode = lastNode; index = properNode.nodeValue.length; } else { // to right of text properNode = lastNode; index = properNode.nodeValue.length; } return new Array(properNode, index); } this.getBoundingRectFor = function(textNode) { var span, bounds; span = makeTempSpan(textNode); textNode.parentNode.replaceChild(span, textNode); bounds = span.getBoundingClientRect(); span.parentNode.replaceChild(textNode, span); return bounds; } this.clickedAbove = function(textNode) { var bounds, top; bounds = this.getBoundingRectFor(textNode); top = Math.round(bounds.top); return (this.y < top); } this.clickedBelow = function(textNode) { var bounds, bottom; bounds = this.getBoundingRectFor(textNode); bottom = Math.round(bounds.bottom); return (this.y > bottom); } } // standalone 'helper' function function makeTempSpan(textNode) { var elem, copyTextNode; copyTextNode = document.createTextNode(textNode.nodeValue); elem = getEmptyTempSpan(); elem.appendChild(copyTextNode); return elem; } // standalone 'helper' function function getEmptyTempSpan() { var elem; elem = document.createElement('span'); elem.setAttribute('class', 'temporarysearchspan'); return elem; } // standalone 'helper' function function rectContains(rect, x, y) { var bool, l, r, t, b; l = rect.left + window.scrollX; r = rect.right + window.scrollX; t = Math.round(rect.top) + window.scrollY; b = Math.round(rect.bottom) + window.scrollY; // possibily rounding issues with Firefox (rect.top & bottom) if (x < l) { bool = false; } else if (x > r) { bool = false; } else if (y < t) { bool = false; } else if (y > b) { bool = false; } else { bool = true; } return bool; } function clickedToLeft(x, y, terminalNodes) { var i, j, rects, r, tempSpan, parent, currNode; for (i = 0; i < terminalNodes.length; i += 1) { currNode = terminalNodes[i]; parent = currNode.parentNode; tempSpan = makeTempSpan(currNode); parent.replaceChild(tempSpan, currNode); rects = tempSpan.getClientRects(); for (j = 0; j < rects.length; j += 1) { r = rects[j]; if (inVerticalBounds(r, y)) { if (toLeftOf(r, x)) { parent.replaceChild(currNode, tempSpan); return true; } } } parent.replaceChild(currNode, tempSpan); } return false; } function inVerticalBounds(rect, y) { var top, bottom top = Math.round(rect.top) + window.scrollY; bottom = Math.round(rect.bottom) + window.scrollY; if (y > top && y < bottom) { return true; } return false; } function toLeftOf(rect, x) { var leftEdge = rect.left + window.scrollX; return (x < leftEdge); }