selection-debug.js revision 5e41377074f3a800d125e664578b36234bf28f7d
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * Wraps some common Selection/Range functionality into a simple object
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @module editor
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @submodule selection
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Wraps some common Selection/Range functionality into a simple object
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @class Selection
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @for Selection
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @constructor
a89ad754cce3cfc8aee71760e10217b54020360dTripp //TODO This shouldn't be there, Y.Node doesn't normalize getting textnode content.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp Y.Selection = function() {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp this.isCollapsed = (sel.compareEndPoints('StartToEnd', sel)) ? false : true;
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp this.anchorNode = this.focusNode = Y.one(sel.parentElement());
a89ad754cce3cfc8aee71760e10217b54020360dTripp this.anchorNode = this.focusNode = Y.Selection.resolve(ieNode);
a89ad754cce3cfc8aee71760e10217b54020360dTripp this.anchorOffset = this.focusOffset = (this.anchorNode.nodeValue) ? this.anchorNode.nodeValue.length : 0 ;
a89ad754cce3cfc8aee71760e10217b54020360dTripp this.anchorTextNode = this.focusTextNode = Y.one(ieNode);
a89ad754cce3cfc8aee71760e10217b54020360dTripp //var self = this;
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp //debugger;
a89ad754cce3cfc8aee71760e10217b54020360dTripp this.anchorNode = Y.Selection.resolve(sel.anchorNode);
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Performs a prefilter on all nodes in the editor. Looks for nodes with a style: fontFamily or font face
a89ad754cce3cfc8aee71760e10217b54020360dTripp * It then creates a dynamic class assigns it and removed the property. This is so that we don't lose
a89ad754cce3cfc8aee71760e10217b54020360dTripp * the fontFamily when selecting nodes.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @method filter
a89ad754cce3cfc8aee71760e10217b54020360dTripp //This is for IE
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp if (n.getAttribute('style').toLowerCase() === 'font-family: ') {
a89ad754cce3cfc8aee71760e10217b54020360dTripp //Not sure about this one?
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp if (t === 'strong') {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.Selection.prototype._swap(baseNodes.item(k), newTag);
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp //Filter out all the empty UL/OL's
a4d2446149b07f9e5c32947091dcbcf4d2eee765Tripp * Method attempts to replace all "orphined" text nodes in the main body by wrapping them with a <p>. Called from filter.
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * @method filterBlocks
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp var childs = Y.config.doc.body.childNodes, i, node, wrapped = false, doit = true,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp c = childs[i].textContent.match(Y.Selection.REG_CHAR);
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp if (c === null && s) {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.log('Only One Paragragh, focus it..', 'info', 'selection');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.log('Empty Paragraph Tag Found, Removing It', 'info', 'selection');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.log('Empty DIV/P Tag Found, Removing It', 'info', 'selection');
a89ad754cce3cfc8aee71760e10217b54020360dTripp Y.log('DIVS/PS Count: ' + d.get('childNodes').size(), 'info', 'selection');
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp Y.log('This Div/P only has one Child Node', 'info', 'selection');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.log('This Div/P is a child of a paragraph, remove it..', 'info', 'selection');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp spans = Y.all('.Apple-style-span, .apple-style-span');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.log('Apple Spans found: ' + spans.size(), 'info', 'selection');
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * Regular Expression to determine if a string has a character in it
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @property REG_CHAR
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp * Regular Expression to determine if a string has a non-character in it
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp * @property REG_NON
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Wraps an array of elements in a Block level tag
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @method _wrapBlock
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp return false;
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Undoes what filter does enough to return the HTML from the Editor, then re-applies the filter.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @method unfilter
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @return {String} The filtered HTML
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp //One of ours
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Resolve a node from the selection object and return a Node instance
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @method resolve
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @param {HTMLElement} n The HTMLElement to resolve. Might be a TextNode, gives parentNode.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @return {Node} The Resolved node
a89ad754cce3cfc8aee71760e10217b54020360dTripp return Y.one(n);
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Returns the innerHTML of a node with all HTML tags removed.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @method getText
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param {Node} node The Node instance to remove the HTML from
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @return {String} The string of text
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp return node.get('innerHTML').replace(Y.Selection.STRIP_HTML, '');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The selector to use when looking for Nodes to cache the value of: [style],font[face]
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @property ALL
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * RegExp used to strip HTML tags from a string
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @property STRIP_HTML
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * The selector to use when looking for block level items.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @property BLOCKS
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * The temporary fontname applied to a selection to retrieve their values: yui-tmp
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp * @property TMP
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * The default tag to use when creating elements: span
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @property DEFAULT_TAG
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp Y.Selection.CURSOR = '<span id="' + Y.Selection.CURID + '"> </span>';
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp * Range text value
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @property text
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type String
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Flag to show if the range is collapsed or not
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @property isCollapsed
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type Boolean
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * A Node instance of the parentNode of the anchorNode of the range
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @property anchorNode
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Node
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * The offset from the range object
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @property anchorOffset
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Number
a89ad754cce3cfc8aee71760e10217b54020360dTripp * A Node instance of the actual textNode of the range.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @property anchorTextNode
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Node
a89ad754cce3cfc8aee71760e10217b54020360dTripp * A Node instance of the parentNode of the focusNode of the range
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @property focusNode
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Node
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The offset from the range object
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @property focusOffset
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Number
a89ad754cce3cfc8aee71760e10217b54020360dTripp * A Node instance of the actual textNode of the range.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @property focusTextNode
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Node
a89ad754cce3cfc8aee71760e10217b54020360dTripp * The actual Selection/Range object
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @property _selection
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Wrap an element, with another element
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @method _wrap
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @param {HTMLElement} n The node to wrap
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @param {String} tag The tag to use when creating the new element.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @return {HTMLElement} The wrapped node
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Swap an element, with another element
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @method _swap
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param {HTMLElement} n The node to swap
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param {String} tag The tag to use when creating the new element.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @return {HTMLElement} The new node
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Get all the nodes in the current selection. This method will actually perform a filter first.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Then it calls doc.execCommand('fontname', null, 'yui-tmp') to touch all nodes in the selection.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The it compiles a list of all nodes affected by the execCommand and builds a NodeList to return.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @method getSelected
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @return {NodeList} A NodeList of all items in the selection.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp getSelected: function() {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.config.doc.execCommand('fontname', null, Y.Selection.TMP);
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * Insert HTML at the current cursor position and return a Node instance of the newly inserted element.
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * @method insertContent
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * @param {String} html The HTML to insert.
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * @return {Node} The inserted Node.
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp return this.insertAtCursor(html, this.anchorTextNode, this.anchorOffset, true);
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * Insert HTML at the current cursor position, this method gives you control over the text node to insert into and the offset where to put it.
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * @method insertAtCursor
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * @param {String} html The HTML to insert.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param {Node} node The text node to break when inserting.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @param {Number} offset The left offset of the text node to break and insert the new content.
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp * @param {Boolean} collapse Should the range be collapsed after insertion. default: false
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @return {Node} The inserted Node.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp insertAtCursor: function(html, node, offset, collapse) {
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp var cur = Y.Node.create('<' + Y.Selection.DEFAULT_TAG + ' class="yui-non"></' + Y.Selection.DEFAULT_TAG + '>'),
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp inHTML, txt, txt2, newNode, range = this.createRange(), b;
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp }, '#rte-insert');
9eaaa502227248d304ac9170902697d02158c1d9Tripp //TODO using Y.Node.create here throws warnings & strips first white space character
9eaaa502227248d304ac9170902697d02158c1d9Tripp //txt = Y.one(Y.Node.create(inHTML.substr(0, offset)));
9eaaa502227248d304ac9170902697d02158c1d9Tripp //txt2 = Y.one(Y.Node.create(inHTML.substr(offset)));
4ef2f7e4cb7c7d255be077c47d542199f7bf8607Tripp txt = Y.one(Y.config.doc.createTextNode(inHTML.substr(0, offset)));
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp txt2 = Y.one(Y.config.doc.createTextNode(inHTML.substr(offset)));
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * Get all elements inside a selection and wrap them with a new element and return a NodeList of all elements touched.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @method wrapContent
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @param {String} tag The tag to wrap all selected items with.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @return {NodeList} A NodeList of all items in the selection.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp if (!this.isCollapsed) {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.log('Wrapping selection with: ' + tag, 'info', 'selection');
a89ad754cce3cfc8aee71760e10217b54020360dTripp if (t === 'font') {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.log('Returning NodeList with (' + changed.size() + ') item(s)' , 'info', 'selection');
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp Y.log('Can not wrap a collapsed selection, use insertContent', 'error', 'selection');
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp return Y.all([]);
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * Find and replace a string inside a text node and replace it with HTML focusing the node after
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * to allow you to continue to type.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @method replace
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param {String} se The string to search for.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @param {String} re The string of HTML to replace it with.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @return {Node} The node inserted.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp var range = this.createRange(), node, txt, index, newNode;
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp txt = this.anchorNode.get('innerHTML').replace(se, re);
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp * Destroy the range.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @method remove
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @chainable
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @return {Y.Selection}
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp remove: function() {
createRange: function() {
if (collapse) {
} catch (err) {
if (collapse) {
setCursor: function() {
getCursor: function() {
* @param {Boolean} keep Setting this to true will keep the node, but remove the unique parts that make it the cursor.
if (cur) {
if (keep) {
return cur;
if (collapse !== false) {
collapse = true;
if (end !== false) {
end = true;
if (cur) {
toString: function() {