3
Copyright 2011 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('selection', function(Y) {
10
* Wraps some common Selection/Range functionality into a simple object
14
* @submodule selection
17
//TODO This shouldn't be there, Y.Node doesn't normalize getting textnode content.
18
var textContent = 'textContent',
19
INNER_HTML = 'innerHTML',
20
FONT_FAMILY = 'fontFamily';
23
textContent = 'nodeValue';
26
Y.Selection = function(domEvent) {
27
var sel, par, ieNode, nodes, rng, i;
29
if (Y.config.win.getSelection) {
30
sel = Y.config.win.getSelection();
31
} else if (Y.config.doc.selection) {
32
sel = Y.config.doc.selection.createRange();
34
this._selection = sel;
37
this.isCollapsed = (sel.compareEndPoints('StartToEnd', sel)) ? false : true;
38
if (this.isCollapsed) {
39
this.anchorNode = this.focusNode = Y.one(sel.parentElement());
42
ieNode = Y.config.doc.elementFromPoint(domEvent.clientX, domEvent.clientY);
44
rng = sel.duplicate();
46
par = sel.parentElement();
47
nodes = par.childNodes;
49
for (i = 0; i < nodes.length; i++) {
50
//This causes IE to not allow a selection on a doubleclick
51
//rng.select(nodes[i]);
52
if (rng.inRange(sel)) {
63
if (ieNode.nodeType !== 3) {
64
if (ieNode.firstChild) {
65
ieNode = ieNode.firstChild;
67
if (ieNode && ieNode.tagName && ieNode.tagName.toLowerCase() === 'body') {
68
if (ieNode.firstChild) {
69
ieNode = ieNode.firstChild;
73
this.anchorNode = this.focusNode = Y.Selection.resolve(ieNode);
75
rng.moveToElementText(sel.parentElement());
76
var comp = sel.compareEndPoints('StartToStart', rng),
79
//We are not at the beginning of the selection.
80
//Setting the move to something large, may need to increase it later
81
moved = Math.abs(sel.move('character', -9999));
84
this.anchorOffset = this.focusOffset = moved;
86
this.anchorTextNode = this.focusTextNode = Y.one(ieNode);
91
//This helps IE deal with a selection and nodeChange events
92
if (sel.htmlText && sel.htmlText !== '') {
93
var n = Y.Node.create(sel.htmlText);
94
if (n && n.get('id')) {
96
this.anchorNode = this.focusNode = Y.one('#' + id);
98
n = n.get('childNodes');
99
this.anchorNode = this.focusNode = n.item(0);
107
this.isCollapsed = sel.isCollapsed;
108
this.anchorNode = Y.Selection.resolve(sel.anchorNode);
109
this.focusNode = Y.Selection.resolve(sel.focusNode);
110
this.anchorOffset = sel.anchorOffset;
111
this.focusOffset = sel.focusOffset;
113
this.anchorTextNode = Y.one(sel.anchorNode);
114
this.focusTextNode = Y.one(sel.focusNode);
116
if (Y.Lang.isString(sel.text)) {
117
this.text = sel.text;
120
this.text = sel.toString();
128
* Utility method to remove dead font-family styles from an element.
130
* @method removeFontFamily
132
Y.Selection.removeFontFamily = function(n) {
133
n.removeAttribute('face');
134
var s = n.getAttribute('style').toLowerCase();
135
if (s === '' || (s == 'font-family: ')) {
136
n.removeAttribute('style');
138
if (s.match(Y.Selection.REG_FONTFAMILY)) {
139
s = s.replace(Y.Selection.REG_FONTFAMILY, '');
140
n.setAttribute('style', s);
145
* Performs a prefilter on all nodes in the editor. Looks for nodes with a style: fontFamily or font face
146
* It then creates a dynamic class assigns it and removed the property. This is so that we don't lose
147
* the fontFamily when selecting nodes.
151
Y.Selection.filter = function(blocks) {
152
var startTime = (new Date()).getTime();
153
Y.log('Filtering nodes', 'info', 'selection');
155
var nodes = Y.all(Y.Selection.ALL),
156
baseNodes = Y.all('strong,em'),
157
doc = Y.config.doc, hrs,
158
classNames = {}, cssString = '',
161
var startTime1 = (new Date()).getTime();
162
nodes.each(function(n) {
163
var raw = Y.Node.getDOMNode(n);
164
if (raw.style[FONT_FAMILY]) {
165
classNames['.' + n._yuid] = raw.style[FONT_FAMILY];
168
Y.Selection.removeFontFamily(raw);
171
if (n.getStyle(FONT_FAMILY)) {
172
classNames['.' + n._yuid] = n.getStyle(FONT_FAMILY);
174
n.removeAttribute('face');
175
n.setStyle(FONT_FAMILY, '');
176
if (n.getAttribute('style') === '') {
177
n.removeAttribute('style');
180
if (n.getAttribute('style').toLowerCase() === 'font-family: ') {
181
n.removeAttribute('style');
186
var endTime1 = (new Date()).getTime();
187
Y.log('Node Filter Timer: ' + (endTime1 - startTime1) + 'ms', 'info', 'selection');
189
Y.all('.hr').addClass('yui-skip').addClass('yui-non');
192
hrs = doc.getElementsByTagName('hr');
193
Y.each(hrs, function(hr) {
194
var el = doc.createElement('div');
195
el.className = 'hr yui-non yui-skip';
197
el.setAttribute('readonly', true);
198
el.setAttribute('contenteditable', false); //Keep it from being Edited
200
hr.parentNode.replaceChild(el, hr);
202
//Had to move to inline style. writes for ie's < 8. They don't render el.setAttribute('style');
204
s.border = '1px solid #ccc';
209
s.marginBottom = '5px';
210
s.marginLeft = '0px';
211
s.marginRight = '0px';
217
Y.each(classNames, function(v, k) {
218
cssString += k + ' { font-family: ' + v.replace(/"/gi, '') + '; }';
220
Y.StyleSheet(cssString, 'editor');
223
//Not sure about this one?
224
baseNodes.each(function(n, k) {
225
var t = n.get('tagName').toLowerCase(),
227
if (t === 'strong') {
230
Y.Selection.prototype._swap(baseNodes.item(k), newTag);
233
//Filter out all the empty UL/OL's
235
ls.each(function(v, k) {
236
var lis = v.all('li');
243
Y.Selection.filterBlocks();
245
var endTime = (new Date()).getTime();
246
Y.log('Filter Timer: ' + (endTime - startTime) + 'ms', 'info', 'selection');
250
* Method attempts to replace all "orphined" text nodes in the main body by wrapping them with a <p>. Called from filter.
252
* @method filterBlocks
254
Y.Selection.filterBlocks = function() {
255
var startTime = (new Date()).getTime();
256
Y.log('RAW filter blocks', 'info', 'selection');
257
var childs = Y.config.doc.body.childNodes, i, node, wrapped = false, doit = true,
258
sel, single, br, divs, spans, c, s;
261
for (i = 0; i < childs.length; i++) {
262
node = Y.one(childs[i]);
263
if (!node.test(Y.Selection.BLOCKS)) {
265
if (childs[i].nodeType == 3) {
266
c = childs[i][textContent].match(Y.Selection.REG_CHAR);
267
s = childs[i][textContent].match(Y.Selection.REG_NON);
268
if (c === null && s) {
277
wrapped.push(childs[i]);
280
wrapped = Y.Selection._wrapBlock(wrapped);
283
wrapped = Y.Selection._wrapBlock(wrapped);
286
single = Y.all(Y.Selection.DEFAULT_BLOCK_TAG);
287
if (single.size() === 1) {
288
Y.log('Only One default block tag (' + Y.Selection.DEFAULT_BLOCK_TAG + '), focus it..', 'info', 'selection');
289
br = single.item(0).all('br');
290
if (br.size() === 1) {
291
if (!br.item(0).test('.yui-cursor')) {
294
var html = single.item(0).get('innerHTML');
295
if (html === '' || html === ' ') {
296
Y.log('Paragraph empty, focusing cursor', 'info', 'selection');
297
single.set('innerHTML', Y.Selection.CURSOR);
298
sel = new Y.Selection();
299
sel.focusCursor(true, true);
301
if (br.item(0).test('.yui-cursor') && Y.UA.ie) {
306
single.each(function(p) {
307
var html = p.get('innerHTML');
309
Y.log('Empty Paragraph Tag Found, Removing It', 'info', 'selection');
317
divs = Y.all('div, p');
318
divs.each(function(d) {
319
if (d.hasClass('yui-non')) {
322
var html = d.get('innerHTML');
324
Y.log('Empty DIV/P Tag Found, Removing It', 'info', 'selection');
327
Y.log('DIVS/PS Count: ' + d.get('childNodes').size(), 'info', 'selection');
328
if (d.get('childNodes').size() == 1) {
329
Y.log('This Div/P only has one Child Node', 'info', 'selection');
330
if (d.ancestor('p')) {
331
Y.log('This Div/P is a child of a paragraph, remove it..', 'info', 'selection');
332
d.replace(d.get('firstChild'));
338
/* Removed this, as it was causing Pasting to be funky in Safari
339
spans = Y.all('.Apple-style-span, .apple-style-span');
340
Y.log('Apple Spans found: ' + spans.size(), 'info', 'selection');
341
spans.each(function(s) {
342
s.setAttribute('style', '');
348
var endTime = (new Date()).getTime();
349
Y.log('FilterBlocks Timer: ' + (endTime - startTime) + 'ms', 'info', 'selection');
353
* Regular Expression used to find dead font-family styles
355
* @property REG_FONTFAMILY
357
Y.Selection.REG_FONTFAMILY = /font-family: ;/;
360
* Regular Expression to determine if a string has a character in it
364
Y.Selection.REG_CHAR = /[a-zA-Z-0-9_!@#\$%\^&*\(\)-=_+\[\]\\{}|;':",.\/<>\?]/gi;
367
* Regular Expression to determine if a string has a non-character in it
371
Y.Selection.REG_NON = /[\s|\n|\t]/gi;
374
* Regular Expression to remove all HTML from a string
376
* @property REG_NOHTML
378
Y.Selection.REG_NOHTML = /<\S[^><]*>/g;
382
* Wraps an array of elements in a Block level tag
387
Y.Selection._wrapBlock = function(wrapped) {
389
var newChild = Y.Node.create('<' + Y.Selection.DEFAULT_BLOCK_TAG + '></' + Y.Selection.DEFAULT_BLOCK_TAG + '>'),
390
firstChild = Y.one(wrapped[0]), i;
392
for (i = 1; i < wrapped.length; i++) {
393
newChild.append(wrapped[i]);
395
firstChild.replace(newChild);
396
newChild.prepend(firstChild);
402
* Undoes what filter does enough to return the HTML from the Editor, then re-applies the filter.
405
* @return {String} The filtered HTML
407
Y.Selection.unfilter = function() {
408
var nodes = Y.all('body [class]'),
409
html = '', nons, ids,
410
body = Y.one('body');
412
Y.log('UnFiltering nodes', 'info', 'selection');
414
nodes.each(function(n) {
415
if (n.hasClass(n._yuid)) {
417
n.setStyle(FONT_FAMILY, n.getStyle(FONT_FAMILY));
418
n.removeClass(n._yuid);
419
if (n.getAttribute('class') === '') {
420
n.removeAttribute('class');
425
nons = Y.all('.yui-non');
426
nons.each(function(n) {
427
if (!n.hasClass('yui-skip') && n.get('innerHTML') === '') {
430
n.removeClass('yui-non').removeClass('yui-skip');
434
ids = Y.all('body [id]');
435
ids.each(function(n) {
436
if (n.get('id').indexOf('yui_3_') === 0) {
437
n.removeAttribute('id');
438
n.removeAttribute('_yuid');
443
html = body.get('innerHTML');
446
Y.all('.hr').addClass('yui-skip').addClass('yui-non');
449
nodes.each(function(n) {
451
n.setStyle(FONT_FAMILY, '');
452
if (n.getAttribute('style') === '') {
453
n.removeAttribute('style');
461
* Resolve a node from the selection object and return a Node instance
464
* @param {HTMLElement} n The HTMLElement to resolve. Might be a TextNode, gives parentNode.
465
* @return {Node} The Resolved node
467
Y.Selection.resolve = function(n) {
468
if (n && n.nodeType === 3) {
469
//Adding a try/catch here because in rare occasions IE will
470
//Throw a error accessing the parentNode of a stranded text node.
471
//In the case of Ctrl+Z (Undo)
482
* Returns the innerHTML of a node with all HTML tags removed.
485
* @param {Node} node The Node instance to remove the HTML from
486
* @return {String} The string of text
488
Y.Selection.getText = function(node) {
489
var txt = node.get('innerHTML').replace(Y.Selection.REG_NOHTML, '');
490
//Clean out the cursor subs to see if the Node is empty
491
txt = txt.replace('<span><br></span>', '').replace('<br>', '');
495
//Y.Selection.DEFAULT_BLOCK_TAG = 'div';
496
Y.Selection.DEFAULT_BLOCK_TAG = 'p';
499
* The selector to use when looking for Nodes to cache the value of: [style],font[face]
503
Y.Selection.ALL = '[style],font[face]';
506
* The selector to use when looking for block level items.
510
Y.Selection.BLOCKS = 'p,div,ul,ol,table,style';
512
* The temporary fontname applied to a selection to retrieve their values: yui-tmp
516
Y.Selection.TMP = 'yui-tmp';
518
* The default tag to use when creating elements: span
520
* @property DEFAULT_TAG
522
Y.Selection.DEFAULT_TAG = 'span';
525
* The id of the outer cursor wrapper
527
* @property DEFAULT_TAG
529
Y.Selection.CURID = 'yui-cursor';
532
* The id used to wrap the inner space of the cursor position
534
* @property CUR_WRAPID
536
Y.Selection.CUR_WRAPID = 'yui-cursor-wrapper';
539
* The default HTML used to focus the cursor..
543
Y.Selection.CURSOR = '<span><br class="yui-cursor"></span>';
545
Y.Selection.hasCursor = function() {
546
var cur = Y.all('#' + Y.Selection.CUR_WRAPID);
547
Y.log('Has Cursor: ' + cur.size(), 'info', 'selection');
552
* Called from Editor keydown to remove the "extra" space before the cursor.
554
* @method cleanCursor
556
Y.Selection.cleanCursor = function() {
557
//Y.log('Cleaning Cursor', 'info', 'Selection');
558
var cur, sel = 'br.yui-cursor';
561
cur.each(function(b) {
562
var c = b.get('parentNode.parentNode.childNodes'), html;
566
html = Y.Selection.getText(c.item(0));
574
var cur = Y.all('#' + Y.Selection.CUR_WRAPID);
576
cur.each(function(c) {
577
var html = c.get('innerHTML');
578
if (html == ' ' || html == '<br>') {
579
if (c.previous() || c.next()) {
588
Y.Selection.prototype = {
596
* Flag to show if the range is collapsed or not
597
* @property isCollapsed
602
* A Node instance of the parentNode of the anchorNode of the range
603
* @property anchorNode
608
* The offset from the range object
609
* @property anchorOffset
614
* A Node instance of the actual textNode of the range.
615
* @property anchorTextNode
618
anchorTextNode: null,
620
* A Node instance of the parentNode of the focusNode of the range
621
* @property focusNode
626
* The offset from the range object
627
* @property focusOffset
632
* A Node instance of the actual textNode of the range.
633
* @property focusTextNode
638
* The actual Selection/Range object
639
* @property _selection
644
* Wrap an element, with another element
647
* @param {HTMLElement} n The node to wrap
648
* @param {String} tag The tag to use when creating the new element.
649
* @return {HTMLElement} The wrapped node
651
_wrap: function(n, tag) {
652
var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
653
tmp.set(INNER_HTML, n.get(INNER_HTML));
654
n.set(INNER_HTML, '');
656
return Y.Node.getDOMNode(tmp);
659
* Swap an element, with another element
662
* @param {HTMLElement} n The node to swap
663
* @param {String} tag The tag to use when creating the new element.
664
* @return {HTMLElement} The new node
666
_swap: function(n, tag) {
667
var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
668
tmp.set(INNER_HTML, n.get(INNER_HTML));
670
return Y.Node.getDOMNode(tmp);
673
* Get all the nodes in the current selection. This method will actually perform a filter first.
674
* Then it calls doc.execCommand('fontname', null, 'yui-tmp') to touch all nodes in the selection.
675
* The it compiles a list of all nodes affected by the execCommand and builds a NodeList to return.
676
* @method getSelected
677
* @return {NodeList} A NodeList of all items in the selection.
679
getSelected: function() {
680
Y.Selection.filter();
681
Y.config.doc.execCommand('fontname', null, Y.Selection.TMP);
682
var nodes = Y.all(Y.Selection.ALL),
685
nodes.each(function(n, k) {
686
if (n.getStyle(FONT_FAMILY) == Y.Selection.TMP) {
687
n.setStyle(FONT_FAMILY, '');
688
Y.Selection.removeFontFamily(n);
689
if (!n.test('body')) {
690
items.push(Y.Node.getDOMNode(nodes.item(k)));
697
* Insert HTML at the current cursor position and return a Node instance of the newly inserted element.
698
* @method insertContent
699
* @param {String} html The HTML to insert.
700
* @return {Node} The inserted Node.
702
insertContent: function(html) {
703
return this.insertAtCursor(html, this.anchorTextNode, this.anchorOffset, true);
706
* 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.
707
* @method insertAtCursor
708
* @param {String} html The HTML to insert.
709
* @param {Node} node The text node to break when inserting.
710
* @param {Number} offset The left offset of the text node to break and insert the new content.
711
* @param {Boolean} collapse Should the range be collapsed after insertion. default: false
712
* @return {Node} The inserted Node.
714
insertAtCursor: function(html, node, offset, collapse) {
715
var cur = Y.Node.create('<' + Y.Selection.DEFAULT_TAG + ' class="yui-non"></' + Y.Selection.DEFAULT_TAG + '>'),
716
inHTML, txt, txt2, newNode, range = this.createRange(), b;
718
if (node && node.test('body')) {
719
b = Y.Node.create('<span></span>');
725
if (range.pasteHTML) {
726
if (offset === 0 && node && !node.previous() && node.get('nodeType') === 3) {
728
* For some strange reason, range.pasteHTML fails if the node is a textNode and
729
* the offset is 0. (The cursor is at the beginning of the line)
730
* It will always insert the new content at position 1 instead of
731
* position 0. Here we test for that case and do it the hard way.
733
node.insert(html, 'before');
734
if (range.moveToElementText) {
735
range.moveToElementText(Y.Node.getDOMNode(node.previous()));
737
//Move the cursor after the new node
738
range.collapse(false);
740
return node.previous();
742
newNode = Y.Node.create(html);
744
range.pasteHTML('<span id="rte-insert"></span>');
746
inHTML = Y.one('#rte-insert');
748
inHTML.set('id', '');
749
inHTML.replace(newNode);
750
if (range.moveToElementText) {
751
range.moveToElementText(Y.Node.getDOMNode(newNode));
753
range.collapse(false);
757
Y.on('available', function() {
758
inHTML.set('id', '');
759
inHTML.replace(newNode);
760
if (range.moveToElementText) {
761
range.moveToElementText(Y.Node.getDOMNode(newNode));
763
range.collapse(false);
769
//TODO using Y.Node.create here throws warnings & strips first white space character
770
//txt = Y.one(Y.Node.create(inHTML.substr(0, offset)));
771
//txt2 = Y.one(Y.Node.create(inHTML.substr(offset)));
773
inHTML = node.get(textContent);
775
txt = Y.one(Y.config.doc.createTextNode(inHTML.substr(0, offset)));
776
txt2 = Y.one(Y.config.doc.createTextNode(inHTML.substr(offset)));
778
node.replace(txt, node);
779
newNode = Y.Node.create(html);
780
if (newNode.get('nodeType') === 11) {
781
b = Y.Node.create('<span></span>');
785
txt.insert(newNode, 'after');
786
//if (txt2 && txt2.get('length')) {
788
newNode.insert(cur, 'after');
789
cur.insert(txt2, 'after');
790
this.selectNode(cur, collapse);
793
if (node.get('nodeType') === 3) {
794
node = node.get('parentNode');
796
newNode = Y.Node.create(html);
797
html = node.get('innerHTML').replace(/\n/gi, '');
798
if (html === '' || html === '<br>') {
799
node.append(newNode);
801
if (newNode.get('parentNode')) {
802
node.insert(newNode, 'before');
804
Y.one('body').prepend(newNode);
807
if (node.get('firstChild').test('br')) {
808
node.get('firstChild').remove();
815
* Get all elements inside a selection and wrap them with a new element and return a NodeList of all elements touched.
816
* @method wrapContent
817
* @param {String} tag The tag to wrap all selected items with.
818
* @return {NodeList} A NodeList of all items in the selection.
820
wrapContent: function(tag) {
821
tag = (tag) ? tag : Y.Selection.DEFAULT_TAG;
823
if (!this.isCollapsed) {
824
Y.log('Wrapping selection with: ' + tag, 'info', 'selection');
825
var items = this.getSelected(),
826
changed = [], range, last, first, range2;
828
items.each(function(n, k) {
829
var t = n.get('tagName').toLowerCase();
831
changed.push(this._swap(items.item(k), tag));
833
changed.push(this._wrap(items.item(k), tag));
837
range = this.createRange();
839
last = changed[changed.length - 1];
840
if (this._selection.removeAllRanges) {
841
range.setStart(changed[0], 0);
842
range.setEnd(last, last.childNodes.length);
843
this._selection.removeAllRanges();
844
this._selection.addRange(range);
846
if (range.moveToElementText) {
847
range.moveToElementText(Y.Node.getDOMNode(first));
848
range2 = this.createRange();
849
range2.moveToElementText(Y.Node.getDOMNode(last));
850
range.setEndPoint('EndToEnd', range2);
855
changed = Y.all(changed);
856
Y.log('Returning NodeList with (' + changed.size() + ') item(s)' , 'info', 'selection');
861
Y.log('Can not wrap a collapsed selection, use insertContent', 'error', 'selection');
866
* Find and replace a string inside a text node and replace it with HTML focusing the node after
867
* to allow you to continue to type.
869
* @param {String} se The string to search for.
870
* @param {String} re The string of HTML to replace it with.
871
* @return {Node} The node inserted.
873
replace: function(se,re) {
874
Y.log('replacing (' + se + ') with (' + re + ')');
875
var range = this.createRange(), node, txt, index, newNode;
877
if (range.getBookmark) {
878
index = range.getBookmark();
879
txt = this.anchorNode.get('innerHTML').replace(se, re);
880
this.anchorNode.set('innerHTML', txt);
881
range.moveToBookmark(index);
882
newNode = Y.one(range.parentElement());
884
node = this.anchorTextNode;
885
txt = node.get(textContent);
886
index = txt.indexOf(se);
888
txt = txt.replace(se, '');
889
node.set(textContent, txt);
890
newNode = this.insertAtCursor(re, node, index, true);
898
* @return {Selection}
901
this._selection.removeAllRanges();
905
* Wrapper for the different range creation methods.
906
* @method createRange
907
* @return {RangeObject}
909
createRange: function() {
910
if (Y.config.doc.selection) {
911
return Y.config.doc.selection.createRange();
913
return Y.config.doc.createRange();
917
* Select a Node (hilighting it).
919
* @param {Node} node The node to select
920
* @param {Boolean} collapse Should the range be collapsed after insertion. default: false
922
* @return {Selection}
924
selectNode: function(node, collapse, end) {
926
Y.log('Node passed to selectNode is null', 'error', 'selection');
930
node = Y.Node.getDOMNode(node);
931
var range = this.createRange();
932
if (range.selectNode) {
933
range.selectNode(node);
934
this._selection.removeAllRanges();
935
this._selection.addRange(range);
938
this._selection.collapse(node, end);
940
this._selection.collapse(node, 0);
944
if (node.nodeType === 3) {
945
node = node.parentNode;
948
range.moveToElementText(node);
951
range.collapse(((end) ? false : true));
958
* Put a placeholder in the DOM at the current cursor position.
962
setCursor: function() {
963
this.removeCursor(false);
964
return this.insertContent(Y.Selection.CURSOR);
967
* Get the placeholder in the DOM at the current cursor position.
971
getCursor: function() {
972
return Y.all('#' + Y.Selection.CURID);
975
* Remove the cursor placeholder from the DOM.
976
* @method removeCursor
977
* @param {Boolean} keep Setting this to true will keep the node, but remove the unique parts that make it the cursor.
980
removeCursor: function(keep) {
981
var cur = this.getCursor();
984
cur.removeAttribute('id');
985
cur.set('innerHTML', '<br class="yui-cursor">');
993
* Gets a stored cursor and focuses it for editing, must be called sometime after setCursor
994
* @method focusCursor
997
focusCursor: function(collapse, end) {
998
if (collapse !== false) {
1001
if (end !== false) {
1004
var cur = this.removeCursor(true);
1006
cur.each(function(c) {
1007
this.selectNode(c, collapse, end);
1012
* Generic toString for logging.
1016
toString: function() {
1017
return 'Selection Object';
1022
}, '3.4.1' ,{skinnable:false, requires:['node']});