[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /** 2 * Selection save and restore module for Rangy. 3 * Saves and restores user selections using marker invisible elements in the DOM. 4 * 5 * Part of Rangy, a cross-browser JavaScript range and selection library 6 * https://github.com/timdown/rangy 7 * 8 * Depends on Rangy core. 9 * 10 * Copyright 2015, Tim Down 11 * Licensed under the MIT license. 12 * Version: 1.3.0 13 * Build date: 10 May 2015 14 */ 15 (function(factory, root) { 16 // No AMD or CommonJS support so we use the rangy property of root (probably the global variable) 17 factory(root.rangy); 18 })(function(rangy) { 19 rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) { 20 var dom = api.dom; 21 var removeNode = dom.removeNode; 22 var isDirectionBackward = api.Selection.isDirectionBackward; 23 var markerTextChar = "\ufeff"; 24 25 function gEBI(id, doc) { 26 return (doc || document).getElementById(id); 27 } 28 29 function insertRangeBoundaryMarker(range, atStart) { 30 var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2); 31 var markerEl; 32 var doc = dom.getDocument(range.startContainer); 33 34 // Clone the Range and collapse to the appropriate boundary point 35 var boundaryRange = range.cloneRange(); 36 boundaryRange.collapse(atStart); 37 38 // Create the marker element containing a single invisible character using DOM methods and insert it 39 markerEl = doc.createElement("span"); 40 markerEl.id = markerId; 41 markerEl.style.lineHeight = "0"; 42 markerEl.style.display = "none"; 43 markerEl.className = "rangySelectionBoundary"; 44 markerEl.appendChild(doc.createTextNode(markerTextChar)); 45 46 boundaryRange.insertNode(markerEl); 47 return markerEl; 48 } 49 50 function setRangeBoundary(doc, range, markerId, atStart) { 51 var markerEl = gEBI(markerId, doc); 52 if (markerEl) { 53 range[atStart ? "setStartBefore" : "setEndBefore"](markerEl); 54 removeNode(markerEl); 55 } else { 56 module.warn("Marker element has been removed. Cannot restore selection."); 57 } 58 } 59 60 function compareRanges(r1, r2) { 61 return r2.compareBoundaryPoints(r1.START_TO_START, r1); 62 } 63 64 function saveRange(range, direction) { 65 var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString(); 66 var backward = isDirectionBackward(direction); 67 68 if (range.collapsed) { 69 endEl = insertRangeBoundaryMarker(range, false); 70 return { 71 document: doc, 72 markerId: endEl.id, 73 collapsed: true 74 }; 75 } else { 76 endEl = insertRangeBoundaryMarker(range, false); 77 startEl = insertRangeBoundaryMarker(range, true); 78 79 return { 80 document: doc, 81 startMarkerId: startEl.id, 82 endMarkerId: endEl.id, 83 collapsed: false, 84 backward: backward, 85 toString: function() { 86 return "original text: '" + text + "', new text: '" + range.toString() + "'"; 87 } 88 }; 89 } 90 } 91 92 function restoreRange(rangeInfo, normalize) { 93 var doc = rangeInfo.document; 94 if (typeof normalize == "undefined") { 95 normalize = true; 96 } 97 var range = api.createRange(doc); 98 if (rangeInfo.collapsed) { 99 var markerEl = gEBI(rangeInfo.markerId, doc); 100 if (markerEl) { 101 markerEl.style.display = "inline"; 102 var previousNode = markerEl.previousSibling; 103 104 // Workaround for issue 17 105 if (previousNode && previousNode.nodeType == 3) { 106 removeNode(markerEl); 107 range.collapseToPoint(previousNode, previousNode.length); 108 } else { 109 range.collapseBefore(markerEl); 110 removeNode(markerEl); 111 } 112 } else { 113 module.warn("Marker element has been removed. Cannot restore selection."); 114 } 115 } else { 116 setRangeBoundary(doc, range, rangeInfo.startMarkerId, true); 117 setRangeBoundary(doc, range, rangeInfo.endMarkerId, false); 118 } 119 120 if (normalize) { 121 range.normalizeBoundaries(); 122 } 123 124 return range; 125 } 126 127 function saveRanges(ranges, direction) { 128 var rangeInfos = [], range, doc; 129 var backward = isDirectionBackward(direction); 130 131 // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched 132 ranges = ranges.slice(0); 133 ranges.sort(compareRanges); 134 135 for (var i = 0, len = ranges.length; i < len; ++i) { 136 rangeInfos[i] = saveRange(ranges[i], backward); 137 } 138 139 // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie 140 // between its markers 141 for (i = len - 1; i >= 0; --i) { 142 range = ranges[i]; 143 doc = api.DomRange.getRangeDocument(range); 144 if (range.collapsed) { 145 range.collapseAfter(gEBI(rangeInfos[i].markerId, doc)); 146 } else { 147 range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc)); 148 range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc)); 149 } 150 } 151 152 return rangeInfos; 153 } 154 155 function saveSelection(win) { 156 if (!api.isSelectionValid(win)) { 157 module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."); 158 return null; 159 } 160 var sel = api.getSelection(win); 161 var ranges = sel.getAllRanges(); 162 var backward = (ranges.length == 1 && sel.isBackward()); 163 164 var rangeInfos = saveRanges(ranges, backward); 165 166 // Ensure current selection is unaffected 167 if (backward) { 168 sel.setSingleRange(ranges[0], backward); 169 } else { 170 sel.setRanges(ranges); 171 } 172 173 return { 174 win: win, 175 rangeInfos: rangeInfos, 176 restored: false 177 }; 178 } 179 180 function restoreRanges(rangeInfos) { 181 var ranges = []; 182 183 // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid 184 // normalization affecting previously restored ranges. 185 var rangeCount = rangeInfos.length; 186 187 for (var i = rangeCount - 1; i >= 0; i--) { 188 ranges[i] = restoreRange(rangeInfos[i], true); 189 } 190 191 return ranges; 192 } 193 194 function restoreSelection(savedSelection, preserveDirection) { 195 if (!savedSelection.restored) { 196 var rangeInfos = savedSelection.rangeInfos; 197 var sel = api.getSelection(savedSelection.win); 198 var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length; 199 200 if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) { 201 sel.removeAllRanges(); 202 sel.addRange(ranges[0], true); 203 } else { 204 sel.setRanges(ranges); 205 } 206 207 savedSelection.restored = true; 208 } 209 } 210 211 function removeMarkerElement(doc, markerId) { 212 var markerEl = gEBI(markerId, doc); 213 if (markerEl) { 214 removeNode(markerEl); 215 } 216 } 217 218 function removeMarkers(savedSelection) { 219 var rangeInfos = savedSelection.rangeInfos; 220 for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) { 221 rangeInfo = rangeInfos[i]; 222 if (rangeInfo.collapsed) { 223 removeMarkerElement(savedSelection.doc, rangeInfo.markerId); 224 } else { 225 removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId); 226 removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId); 227 } 228 } 229 } 230 231 api.util.extend(api, { 232 saveRange: saveRange, 233 restoreRange: restoreRange, 234 saveRanges: saveRanges, 235 restoreRanges: restoreRanges, 236 saveSelection: saveSelection, 237 restoreSelection: restoreSelection, 238 removeMarkerElement: removeMarkerElement, 239 removeMarkers: removeMarkers 240 }); 241 }); 242 243 return rangy; 244 }, this);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |