r110457 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r110456‎ | r110457 | r110458 >
Date:02:46, 1 February 2012
Author:christian
Status:deferred
Tags:visualeditor 
Comment:
adding range pixel position detection
Modified paths:
  • /trunk/extensions/VisualEditor/contentEditable/index.php (modified) (history)
  • /trunk/extensions/VisualEditor/contentEditable/rangy/rangy-position.js (added) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/contentEditable/rangy/rangy-position.js
@@ -0,0 +1,364 @@
 2+/**
 3+ * @license Position module for Rangy.
 4+ * Extensions to Range and Selection objects to provide access to pixel positions relative to the viewport or document.
 5+ *
 6+ * Part of Rangy, a cross-browser JavaScript range and selection library
 7+ * http://code.google.com/p/rangy/
 8+ *
 9+ * Depends on Rangy core.
 10+ *
 11+ * Copyright %%build:year%%, Tim Down
 12+ * Licensed under the MIT license.
 13+ * Version: %%build:version%%
 14+ * Build date: %%build:date%%
 15+ */
 16+rangy.createModule("Coordinates", function(api, module) {
 17+ api.requireModules( ["WrappedSelection", "WrappedRange"] );
 18+
 19+ var NUMBER = "number";
 20+ var WrappedRange = api.WrappedRange;
 21+ var dom = api.dom, util = api.util;
 22+
 23+ // Since Rangy can deal with multiple documents, we have to do the checks every time, unless we cache a
 24+ // getScrollPosition function in each document. This would necessarily pollute the document's global
 25+ // namespace, which I'm choosing to view as a greater evil than a slight performance hit.
 26+ function getScrollPosition(win) {
 27+ var x = 0, y = 0;
 28+ if (typeof win.pageXOffset == NUMBER && typeof win.pageYOffset == NUMBER) {
 29+ x = win.pageXOffset;
 30+ y = win.pageYOffset;
 31+ } else {
 32+ var doc = win.document;
 33+ var docEl = doc.documentElement;
 34+ var compatMode = doc.compatMode;
 35+ var scrollEl = (typeof compatMode == "string" && compatMode.indexOf("CSS") >= 0 && docEl)
 36+ ? docEl : dom.getBody(doc);
 37+
 38+ if (scrollEl && typeof scrollEl.scrollLeft == NUMBER && typeof scrollEl.scrollTop == NUMBER) {
 39+ try {
 40+ x = scrollEl.scrollLeft;
 41+ y = scrollEl.scrollTop;
 42+ } catch (ex) {}
 43+ }
 44+ }
 45+ return { x: x, y: y };
 46+ }
 47+
 48+ function getAncestorElement(node, tagName) {
 49+ tagName = tagName.toLowerCase();
 50+ while (node) {
 51+ if (node.nodeType == 1 && node.tagName.toLowerCase() == tagName) {
 52+ return node;
 53+ }
 54+ node = node.parentNode;
 55+ }
 56+ return null;
 57+ }
 58+
 59+ function Rect(top, right, bottom, left) {
 60+ this.top = top;
 61+ this.right = right;
 62+ this.bottom = bottom;
 63+ this.left = left;
 64+ this.width = right - left;
 65+ this.height = bottom - top;
 66+ }
 67+
 68+ function createRelativeRect(rect, dx, dy) {
 69+ return new Rect(rect.top + dy, rect.right + dx, rect.bottom + dy, rect.left + dx);
 70+ }
 71+
 72+ function adjustClientRect(rect, doc) {
 73+ // Older IEs have an issue with a two pixel margin on the body element
 74+ var dx = 0, dy = 0;
 75+ var docEl = doc.documentElement, body = dom.getBody(doc);
 76+ var container = (docEl.clientWidth === 0 && typeof body.clientTop == NUMBER) ? body : docEl;
 77+ var clientLeft = container.clientLeft, clientTop = container.clientTop;
 78+ if (clientLeft) {
 79+ dx = -clientLeft;
 80+ }
 81+ if (clientTop) {
 82+ dy = -clientTop;
 83+ }
 84+ return createRelativeRect(rect, dx, dy);
 85+ }
 86+
 87+ function mergeRects(rects) {
 88+ var tops = [], bottoms = [], lefts = [], rights = [];
 89+ for (var i = 0, len = rects.length, rect; i < len; ++i) {
 90+ rect = rects[i];
 91+ if (rect) {
 92+ tops.push(rect.top);
 93+ bottoms.push(rect.bottom);
 94+ lefts.push(rect.left);
 95+ rights.push(rect.right);
 96+ }
 97+ }
 98+ return new Rect(
 99+ Math.min.apply(Math, tops),
 100+ Math.max.apply(Math, rights),
 101+ Math.max.apply(Math, bottoms),
 102+ Math.min.apply(Math, lefts)
 103+ );
 104+ }
 105+
 106+ (function() {
 107+
 108+ // Test that <span> elements support getBoundingClientRect
 109+ var span = document.createElement("span");
 110+ var elementSupportsGetBoundingClientRect = util.isHostMethod(span, "getBoundingClientRect");
 111+ span = null;
 112+
 113+ // Test for getBoundingClientRect support in Range
 114+ var rangeSupportsGetClientRects = false, rangeSupportsGetBoundingClientRect = false;
 115+ if (api.features.implementsDomRange) {
 116+ var testRange = api.createNativeRange();
 117+ rangeSupportsGetClientRects = util.isHostMethod(testRange, "getClientRects");
 118+ rangeSupportsGetBoundingClientRect = util.isHostMethod(testRange, "getBoundingClientRect");
 119+ testRange.detach();
 120+ }
 121+
 122+ util.extend(api.features, {
 123+ rangeSupportsGetBoundingClientRect: rangeSupportsGetBoundingClientRect,
 124+ rangeSupportsGetClientRects: rangeSupportsGetClientRects,
 125+ elementSupportsGetBoundingClientRect: elementSupportsGetBoundingClientRect
 126+ });
 127+
 128+ var createClientBoundaryPosGetter = function(isStart) {
 129+ return function() {
 130+ var boundaryRange = this.cloneRange();
 131+ boundaryRange.collapse(isStart);
 132+ var rect = boundaryRange.getBoundingClientRect();
 133+ return { x: rect[isStart ? "left" : "right"], y: rect[isStart ? "top" : "bottom"] };
 134+ };
 135+ };
 136+
 137+ var rangeProto = api.rangePrototype;
 138+
 139+ if (api.features.implementsTextRange && elementSupportsGetBoundingClientRect) {
 140+ rangeProto.getBoundingClientRect = function() {
 141+ // We need a TextRange
 142+ var textRange = WrappedRange.rangeToTextRange(this);
 143+
 144+ // Work around table problems (table cell bounding rects seem not to count if TextRange spans cells)
 145+ var cells = this.getNodes([1], function(el) {
 146+ return /^t[dh]$/i.test(el.tagName);
 147+ });
 148+
 149+ // Merge rects for each cell selected by the range into overall rect
 150+ var rect, rects = [];
 151+ if (cells.length > 0) {
 152+ var lastTable = getAncestorElement(this.startContainer, "table");
 153+
 154+ for (var i = 0, cell, tempTextRange, table, subRange, subRect; cell = cells[i]; ++i) {
 155+ // Handle non-table sections of the range
 156+ table = getAncestorElement(cell, "table");
 157+ if (!lastTable || table != lastTable) {
 158+ // There is a section of the range prior to the current table, or lying between tables.
 159+ // Merge in its rect
 160+ subRange = this.cloneRange();
 161+ if (lastTable) {
 162+ subRange.setStartAfter(lastTable);
 163+ }
 164+ subRange.setEndBefore(table);
 165+ rects.push(WrappedRange.rangeToTextRange(subRange).getBoundingClientRect());
 166+ }
 167+
 168+ if (this.containsNode(cell)) {
 169+ rects.push(cell.getBoundingClientRect());
 170+ } else {
 171+ tempTextRange = textRange.duplicate();
 172+ tempTextRange.moveToElementText(cell);
 173+ if (tempTextRange.compareEndPoints("StartToStart", textRange) == -1) {
 174+ tempTextRange.setEndPoint("StartToStart", textRange);
 175+ } else if (tempTextRange.compareEndPoints("EndToEnd", textRange) == 1) {
 176+ tempTextRange.setEndPoint("EndToEnd", textRange);
 177+ }
 178+ rects.push(tempTextRange.getBoundingClientRect());
 179+ }
 180+ lastTable = table;
 181+ }
 182+
 183+ // Merge in the rect for any content lying after the final table
 184+ var endTable = getAncestorElement(this.endContainer, "table");
 185+ if (!endTable && lastTable) {
 186+ subRange = this.cloneRange();
 187+ subRange.setStartAfter(lastTable);
 188+ rects.push(WrappedRange.rangeToTextRange(subRange).getBoundingClientRect());
 189+ }
 190+ rect = mergeRects(rects);
 191+ } else {
 192+ rect = textRange.getBoundingClientRect();
 193+ }
 194+
 195+ return adjustClientRect(rect, dom.getDocument(this.startContainer));
 196+ };
 197+ } else if (api.features.implementsDomRange) {
 198+ var createWrappedRange = function(range) {
 199+ return (range instanceof WrappedRange) ? range : new WrappedRange(range);
 200+ };
 201+
 202+ if (rangeSupportsGetBoundingClientRect) {
 203+ rangeProto.getBoundingClientRect = function() {
 204+ var nativeRange = createWrappedRange(this).nativeRange;
 205+ // Test for WebKit getBoundingClientRect bug (https://bugs.webkit.org/show_bug.cgi?id=65324)
 206+ var rect = nativeRange.getBoundingClientRect() || nativeRange.getClientRects()[0];
 207+ return adjustClientRect(rect, dom.getDocument(this.startContainer));
 208+ };
 209+
 210+ if (rangeSupportsGetClientRects) {
 211+ createClientBoundaryPosGetter = function(isStart) {
 212+ return function() {
 213+ var rect, nativeRange = createWrappedRange(this).nativeRange;
 214+ if (isStart) {
 215+ rect = nativeRange.getClientRects()[0];
 216+ return { x: rect.left, y: rect.top };
 217+ } else {
 218+ var rects = nativeRange.getClientRects();
 219+ rect = rects[rects.length - 1];
 220+ return { x: rect.right, y: rect.bottom };
 221+ }
 222+ };
 223+ }
 224+ }
 225+ } else {
 226+ var getElementBoundingClientRect = elementSupportsGetBoundingClientRect ?
 227+ function(el) {
 228+ return adjustClientRect(el.getBoundingClientRect(), dom.getDocument(el));
 229+ } :
 230+
 231+ // This implementation is very naive. There are many browser quirks that make it extremely
 232+ // difficult to get accurate element coordinates in all situations
 233+ function(el) {
 234+ var x = 0, y = 0, offsetEl = el, width = el.offsetWidth, height = el.offsetHeight;
 235+ while (offsetEl) {
 236+ x += offsetEl.offsetLeft;
 237+ y += offsetEl.offsetTop;
 238+ offsetEl = offsetEl.offsetParent;
 239+ }
 240+
 241+ return adjustClientRect(new Rect(y, x + width, y + height, x), dom.getDocument(el));
 242+ };
 243+
 244+ var getRectFromBoundaries = function(range) {
 245+ var rect;
 246+ range.splitBoundaries();
 247+ var span = document.createElement("span");
 248+
 249+ if (range.collapsed) {
 250+ range.insertNode(span);
 251+ rect = getElementBoundingClientRect(span);
 252+ span.parentNode.removeChild(span);
 253+ } else {
 254+ // TODO: This isn't right. I'm not sure it can be made right sensibly. Consider what to do.
 255+ // This doesn't consider all the line boxes it needs to consider.
 256+ var workingRange = range.cloneRange();
 257+
 258+ // Get the start rectangle
 259+ workingRange.collapse(true);
 260+ workingRange.insertNode(span);
 261+ var startRect = getElementBoundingClientRect(span);
 262+ span.parentNode.removeChild(span);
 263+
 264+ // Get the end rectangle
 265+ workingRange.collapseToPoint(range.endContainer, range.endOffset);
 266+ workingRange.insertNode(span);
 267+ var endRect = getElementBoundingClientRect(span);
 268+ span.parentNode.removeChild(span);
 269+
 270+ // Merge the start and end rects
 271+ var rects = [startRect, endRect];
 272+
 273+ // Merge in rectangles for all elements in the range
 274+ var elements = range.getNodes([1], function(el) {
 275+ return range.containsNode(el);
 276+ });
 277+
 278+ for (var i = 0, len = elements.length; i < len; ++i) {
 279+ rects.push(getElementBoundingClientRect(elements[i]));
 280+ }
 281+ rect = mergeRects(rects)
 282+ }
 283+
 284+ // Clean up
 285+ range.normalizeBoundaries();
 286+ return rect;
 287+ };
 288+
 289+ rangeProto.getBoundingClientRect = function(range) {
 290+ return getRectFromBoundaries(createWrappedRange(range));
 291+ };
 292+ }
 293+
 294+ function createDocumentBoundaryPosGetter(isStart) {
 295+ return function() {
 296+ var pos = this["get" + (isStart ? "Start" : "End") + "ClientPos"]();
 297+ var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) );
 298+ return { x: pos.x + scrollPos.x, y: pos.y + scrollPos.y };
 299+ };
 300+ }
 301+ }
 302+
 303+ util.extend(rangeProto, {
 304+ getBoundingDocumentRect: function() {
 305+ var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) );
 306+ return createRelativeRect(this.getBoundingClientRect(), scrollPos.x, scrollPos.y);
 307+ },
 308+
 309+ getStartClientPos: createClientBoundaryPosGetter(true),
 310+ getEndClientPos: createClientBoundaryPosGetter(false),
 311+
 312+ getStartDocumentPos: createDocumentBoundaryPosGetter(true),
 313+ getEndDocumentPos: createDocumentBoundaryPosGetter(false)
 314+ });
 315+ })();
 316+
 317+ // Add Selection methods
 318+ (function() {
 319+ function compareRanges(r1, r2) {
 320+ return r1.compareBoundaryPoints(r2.START_TO_START, r2);
 321+ }
 322+
 323+ function createSelectionRectGetter(isDocument) {
 324+ return function() {
 325+ var rangeMethodName = "getBounding" + (isDocument ? "Document" : "Client") + "Rect";
 326+ var rects = [];
 327+ for (var i = 0, rect = null, rangeRect; i < this.rangeCount; ++i) {
 328+ rects.push(this.getRangeAt(i)[rangeMethodName]());
 329+ }
 330+ return mergeRects(rects);
 331+ };
 332+ }
 333+
 334+ function createSelectionBoundaryPosGetter(isStart, isDocument) {
 335+ return function() {
 336+ if (this.rangeCount == 0) {
 337+ return null;
 338+ }
 339+
 340+ var posType = isDocument ? "Document" : "Client";
 341+
 342+ var ranges = this.getAllRanges();
 343+ if (ranges.length > 1) {
 344+ // Order the ranges by position within the DOM
 345+ ranges.sort(compareRanges);
 346+ }
 347+
 348+ return isStart ?
 349+ ranges[0]["getStart" + posType + "Pos"]() :
 350+ ranges[ranges.length - 1]["getEnd" + posType + "Pos"]();
 351+ };
 352+ }
 353+
 354+ util.extend(api.selectionPrototype, {
 355+ getBoundingClientRect: createSelectionRectGetter(false),
 356+ getBoundingDocumentRect: createSelectionRectGetter(true),
 357+
 358+ getStartClientPos: createSelectionBoundaryPosGetter(true, false),
 359+ getEndClientPos: createSelectionBoundaryPosGetter(false, false),
 360+
 361+ getStartDocumentPos: createSelectionBoundaryPosGetter(true, true),
 362+ getEndDocumentPos: createSelectionBoundaryPosGetter(false, true)
 363+ });
 364+ })();
 365+});
Property changes on: trunk/extensions/VisualEditor/contentEditable/rangy/rangy-position.js
___________________________________________________________________
Added: svn:eol-style
1366 + native
Index: trunk/extensions/VisualEditor/contentEditable/index.php
@@ -45,9 +45,12 @@
4646
4747 <!-- Rangy -->
4848 <script src="rangy/rangy-core.js"></script>
 49+ <script src="rangy/rangy-position.js"></script>
 50+ <!--
4951 <script src="rangy/rangy-cssclassapplier.js"></script>
5052 <script src="rangy/rangy-selectionsaverestore.js"></script>
5153 <script src="rangy/rangy-serializer.js"></script>
 54+ -->
5255
5356 <!-- EditSurface -->
5457 <script src="../modules/jquery/jquery.js"></script>

Status & tagging log