Option name | Type | Description |
---|---|---|
win | Window | window in which text selection is processed |
Text selection class.
Serves as a helper to manage current selection
The object cannot be reused, if the selection changes some of its properties may contain information related to previous selection
function TextSelection(win) {
if (! this instanceof TextSelection)
return new TextSelection(win);
this.window = win || window;
this.init();
}
TextSelection instance method
Returns selection start element
var TextSelection$startElement =
_.partial(_getElement, '_startElement', 'startContainer');
TextSelection instance method
Returns selection end element
var TextSelection$endElement =
_.partial(_getElement, '_endElement', 'endContainer');
TextSelection instance method
Returns selection end element
var TextSelection$containingElement =
_.partial(_getElement, '_containingElement', 'commonAncestorContainer');
TextSelection instance method
Returns selection start Component
var TextSelection$startComponent =
_.partial(_getComponent, '_startComponent', 'startElement');
TextSelection instance method
Returns selection end Component
var TextSelection$endComponent =
_.partial(_getComponent, '_endComponent', 'endElement');
TextSelection instance method
Returns selection end Component
var TextSelection$containingComponent =
_.partial(_getComponent, '_containingComponent', 'containingElement');
_.extendProto(TextSelection, {
init: TextSelection$init,
text: TextSelection$text,
textNodes: TextSelection$textNodes,
clear: TextSelection$clear,
startElement: TextSelection$startElement,
endElement: TextSelection$endElement,
containingElement: TextSelection$containingElement,
startComponent: TextSelection$startComponent,
endComponent: TextSelection$endComponent,
containingComponent: TextSelection$containingComponent,
containedComponents: TextSelection$containedComponents,
eachContainedComponent: TextSelection$eachContainedComponent,
del: TextSelection$del,
_getPostDeleteSelectionPoint: _getPostDeleteSelectionPoint,
_selectAfterDelete: _selectAfterDelete,
getRange: TextSelection$getRange,
getState: TextSelection$getState,
getNormalizedRange: TextSelection$$getNormalizedRange,
getDirection: TextSelection$$getDirection
});
_.extend(TextSelection, {
createFromRange: TextSelection$$createFromRange,
createFromState: TextSelection$$createFromState,
createStateObject: TextSelection$$createStateObject
});
TextSelection instance method
Initializes TextSelection from the current selection
function TextSelection$init() {
this.selection = this.window.getSelection();
if (this.selection.rangeCount)
this.range = this.selection.getRangeAt(0);
this.isCollapsed = this.selection.isCollapsed;
}
TextSelection instance method
Retrieves and returns selection text
function TextSelection$text() {
if (! this.range) return undefined;
if (! this._text)
this._text = this.range.toString();
return this._text;
}
TextSelection instance method
Retrieves and returns selection text nodes
function TextSelection$textNodes() {
if (! this.range) return undefined;
if (! this._textNodes)
this._textNodes = _getTextNodes.call(this);
return this._textNodes;
}
function TextSelection$clear() {
this.selection.removeAllRanges();
}
Option name | Type | Description |
---|---|---|
selectEndContainer | Boolean | set to true if the end container should be selected after deletion |
TextSelection instance method
Deletes the current selection and all components in it
function TextSelection$del(selectEndContainer) {
if (this.isCollapsed || ! this.range) return;
var selPoint = this._getPostDeleteSelectionPoint(selectEndContainer);
deleteRangeWithComponents(this.range);
this._selectAfterDelete(selPoint);
selPoint.node.parentNode.normalize();
}
function _getPostDeleteSelectionPoint(selectEndContainer) {
var selNode = this.range.startContainer;
var selOffset = this.range.startOffset;
if (selectEndContainer && this.range.startContainer != this.range.endContainer) {
selNode = this.range.endContainer;
selOffset = 0;
}
return { node: selNode, offset: selOffset };
}
function _selectAfterDelete(selPoint) {
var selNode = selPoint.node
, selOffset = selPoint.offset;
if (!selNode) return;
if (selNode.nodeType == Node.TEXT_NODE)
selNode.textContent = selNode.textContent.trimRight();
if (!selNode.nodeValue)
selNode.nodeValue = '\u00A0'; //non-breaking space, \u200B for zero width space;
var position = selOffset > selNode.length ? selNode.length : selOffset;
setCaretPosition(selNode, position);
}
Returns selection range
function TextSelection$getRange() {
return this.range;
}
Stores selection window, nodes and offsets in object
function TextSelection$getState(rootEl) {
var r = this.range;
var doc = rootEl.ownerDocument
, win = doc.defaultView || doc.parentWindow;
if (!r) return { window: win };
return TextSelection.createStateObject(rootEl, r.startContainer, r.startOffset, r.endContainer, r.endOffset);
}
function TextSelection$$createStateObject(rootEl, startContainer, startOffset, endContainer, endOffset) {
endContainer = endContainer || startContainer;
endOffset = endOffset || startOffset;
var doc = rootEl.ownerDocument
, win = doc.defaultView || doc.parentWindow;
return {
window: win,
rootEl: rootEl,
start: _getSelectionPointState(rootEl, startContainer, startOffset),
end: _getSelectionPointState(rootEl, endContainer, endOffset)
};
}
function _getSelectionPointState(rootEl, node, offset) {
var treePath = domUtils.treePathOf(rootEl, node);
if (! treePath) logger.error('Selection point is outside of root element');
return {
treePath: treePath,
offset: offset
};
}
Restores actual selection to the stored range
function TextSelection$$createFromState(state) {
var domUtils = state.window.milo.util.dom;
if (state.rootEl && state.start && state.end) {
var startNode = _selectionNodeFromState(state.rootEl, state.start)
, endNode = _selectionNodeFromState(state.rootEl, state.end);
try {
domUtils.setSelection(startNode, state.start.offset, endNode, state.end.offset);
return new TextSelection(state.window);
} catch(e) {
logger.error('Text selection: can\'t create selection', e, e.message);
}
} else {
domUtils.clearSelection(state.window);
return new TextSelection(state.window);
}
}
function _selectionNodeFromState(rootEl, pointState) {
var node = domUtils.getNodeAtTreePath(rootEl, pointState.treePath);
if (! node) logger.error('TextSelection createFromState: no node at treePath');
return node;
}
/**
* Creates selection from passed range
*
* @param {Range} range
* @param {Boolean} backward
*
* @return {TextSelection}
*/
function TextSelection$$createFromRange(range, backward) {
var win = range.startContainer.ownerDocument.defaultView
, sel = win.getSelection()
, endRange;
sel.removeAllRanges();
if (backward){
endRange = range.cloneRange();
endRange.collapse(false);
sel.addRange(endRange);
sel.extend(range.startContainer, range.startOffset);
}
else {
sel.addRange(range);
}
return new TextSelection(win);
}
/**
* Returns a normalized copy of the range
* If you triple click an item, the end of the range is positioned at the beginning of the NEXT node.
* this function returns a range with the end positioned at the end of the last textnode contained
* inside a component with the "editable" facet
*
* @return {range}
*/
function TextSelection$$getNormalizedRange(){
var doc = this.range.commonAncestorContainer.ownerDocument
, tw, previousNode
, newRange = this.range.cloneRange();
if (newRange.endContainer.nodeType !== Node.TEXT_NODE) {
tw = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT);
tw.currentNode = newRange.endContainer;
previousNode = tw.previousNode();
newRange.setEnd(previousNode, previousNode.length);
}
return newRange;
}
/**
* get the direction of a selection
*
* 1 forward, -1 backward, 0 no direction, undefined one of the node is detached or in a different frame
*
* @return {Integer} can be -1, 0, 1 or undefined
*/
function TextSelection$$getDirection(){
return domUtils.getSelectionDirection(this.selection);
}