/** * @fileOverview * native selection, range 객체를 wrapping 한 객체로 Processor 에서 주로 사용된다. */ Trex.I.Selection = {}; Trex.I.Selection.Standard = /** @lends Trex.Canvas.Selection.prototype */{ /** * native selection object를 리턴한다. * @returns {Object} - native selection object * @example * txSelection.getSel(); */ getSel: function(){ return this.win.getSelection(); }, /** * 선택된 영역의 텍스트 데이터를 리턴한다. * @returns {String} - 선택된 영역의 텍스트 데이터 * @example * txSelection.getText(); */ getText: function() { return this.getSel().toString(); }, /** * 선택된 영역의 노드를 리턴한다. * @returns {Element} - 선택된 영역의 노드 * @example * txSelection.getNode(); */ getNode: function() { var _rng = this.getRange(); if (_rng) { var _startContainer = _rng.startContainer; if (_startContainer.nodeType == 1) { if ($tom.isBody(_startContainer)) { return (_startContainer); } else { return (_startContainer.childNodes[_rng.startOffset]); } } else { return (_startContainer.parentNode); } } else { return _NULL; } }, /** * native range 를 생성한다. * @returns {Object} - native range 객체 * @example * txSelection.createRange(); */ createRange: function() { return this.doc.createRange(); }, /** * native text range 를 생성한다. * @returns {Object} - native text range 객체 * @example * txSelection.createTextRange(); */ createTextRange: function() { return this.doc.createRange(); }, /** * native range object를 리턴한다. * @returns {Object} - native range 객체 * @example * txSelection.getRange(); */ getRange: function(collapse) { var _sel = this.getSel(); if (_sel && _sel.rangeCount > 0) { if (collapse == _NULL) { if (_sel.rangeCount == 1) { //단일 Range = 일반적인 경우 return _sel.getRangeAt(0); } else { //복수 Range -> 단일 Range로 변환 return this.mergeRange(_sel); } } else { //Range를 collapse할 경우 var _rng = _sel.getRangeAt(0); _rng.collapse(collapse); return _rng; } } else { //Range가 없을 경우 return this.doc.createRange(); } }, /** * 선택된 영역의 collapse 여부(선택된 영역이 있는지 여부)를 리턴한다. * @returns {Boolean} - collapse 여부 * @example * txSelection.isCollapsed(); */ isCollapsed: function() { var _sel = this.getSel(); return (_sel && _sel.isCollapsed); }, /** * 선택된 영역을 collapse 시킨다. * @param {Boolean} toStart - 위치, 시작 = true * @example * txSelection.collapse(true); */ collapse: function(toStart) { var _sel = this.getSel(); if (_sel && _sel.rangeCount > 0) { var _rng = _sel.getRangeAt(0); _rng.collapse(toStart); } }, /** * 선택된 영역의 컨트롤 노드(img,object,hr,table,button)를 리턴한다. * @returns {Element} - 선택된 영역의 노드 * @example * txSelection.getControl(); */ getControl: function() { var _sel = this.getSel(); var _node; if ($tx.opera) { /* @opera IMG 선택시 isCollapsed 가 true 되는 문제가 있음. */ _node = _sel.anchorNode.childNodes[_sel.anchorOffset]; if (_node == _NULL) { return _NULL; } if(_sel.isCollapsed && _node.tagName != "IMG") { return _NULL; } } else { if(_sel.isCollapsed) { return _NULL; } _node = _sel.anchorNode.childNodes[_sel.anchorOffset]; } if($tom.kindOf(_node, '%control')) { return _node; } else { return _NULL; } }, /** * 선택된 영역이 컨트롤 노드인지 여부를 리턴한다. * @returns {Boolean} - 컨트롤 노드인지 여부 * @example * txSelection.hasControl(); */ hasControl: function() { return (this.getControl() != _NULL); }, /** * 컨트롤 노드를 선택한다. * @param {Element} node - 컨트롤 노트 * @example * txSelection.selectControl(node); */ selectControl: function(node) { var _rng = this.createRange(); _rng.selectNode(node); var _sel = this.getSel(); _sel.removeAllRanges(); _sel.addRange(_rng); }, /** * 선택된 영역이 텍스트 데이터 영역의 어떤 위치인지를 리턴한다. * @returns {Number} - 텍스트 데이터 영역의 어떤 위치인지
* 빈 텍스트일 경우 : $tom.__POSITION.__EMPTY_TEXT : -2
* 텍스트의 처음 : $tom.__POSITION.__START_OF_TEXT : -1
* 텍스트의 중간 : $tom.__POSITION.__MIDDLE_OF_TEXT : 0
* 텍스트의 마지막 : $tom.__POSITION.__END_OF_TEXT : 1 * @example * txSelection.compareTextPos(); */ compareTextPos: function() { var _rng = this.getRange(); if (_rng) { var _startContainer = _rng.startContainer; if (_startContainer.nodeType == 3) { if ( _startContainer.textContent.trim().length == 0 ){ return $tom.__POSITION.__EMPTY_TEXT; }else if(_rng.startOffset == 0 ) { return $tom.__POSITION.__START_OF_TEXT; } else if(_rng.startOffset == _startContainer.textContent.length) { return $tom.__POSITION.__END_OF_TEXT; } else { return $tom.__POSITION.__MIDDLE_OF_TEXT; } } } return $tom.__POSITION.__END_OF_TEXT; }, /** * @private * selection에 복수의 range가 있을 경우 range를 합친디ㅏ. * @returns {Object} - native range 객체 * @example * txSelection.mergeRange(sel); */ mergeRange: function(sel) { try { var _ranges = []; for(var i=0,_length=sel.rangeCount; i<_length; i++) { _ranges.push(sel.getRangeAt(i)); } sel.removeAllRanges(); var _startNode = _ranges[0].startContainer.childNodes[_ranges[0].startOffset]; var _endNode = _ranges[_length - 1].endContainer.childNodes[_ranges[_length - 1].endOffset - 1]; var _rng = this.doc.createRange(); try { _rng.setStart(_startNode, 0); } catch (e) { _rng.collapse(_TRUE); } try { _rng.setEnd(_endNode, _endNode.childNodes.length); } catch (e) {} sel.addRange(_rng); return sel.getRangeAt(0); } catch(e) { return sel.getRangeAt(0); } }, /** * @private * 특정 위치로 range의 시작위치를 지정한다. * @param {Object} rng - native range 객체 * @param {Element} node - 특정 부모 노드 * @param {Number} offset - 노드의 옵셋 * @example * txSelection.setStart(range, node, 1); */ setStart: function(rng, node, offset) { try { rng.setStart(node, offset); } catch (e) { rng.collapse(_TRUE); rng.setStart(node, offset); } }, /** * @private * 특정 위치로 range의 끝위치를 지정한다. * @param {Object} rng - native range 객체 * @param {Element} node - 특정 부모 노드 * @param {Number} offset - 노드의 옵셋 * @example * txSelection.setEnd(range, node, 1); */ setEnd: function(rng, node, offset) { try { rng.setEnd(node, offset); } catch (e) { rng.collapse(_FALSE); rng.setEnd(node, offset); } }, /** * 주어진 range를 선택한다. * @returns {Object} - native selection 객체 * @example * txSelection.selectRange(range); */ selectRange: function(rng) { var _sel = this.getSel(); _sel.removeAllRanges(); _sel.addRange(rng); } }; Trex.I.Selection.Trident = /** @lends Trex.Canvas.Selection.prototype */{ /** * native selection object를 리턴한다. * @returns {Object} - native selection object * @example * txSelection.getSel(); */ getSel: function(){ return this.doc.selection; }, /** * 선택된 영역의 텍스트 데이터를 리턴한다. * @returns {String} - 선택된 영역의 텍스트 데이터 * @example * txSelection.getText(); */ getText: function() { return this.getSel().createRange().text; }, /** * 선택된 영역의 노드를 리턴한다. * @returns {Element} - 선택된 영역의 노드 * @example * txSelection.getNode(); */ getNode: function() { var _sel = this.getSel(); var _type = _sel.type.toLowerCase(); if (_type === "control") { return (_sel.createRange().item(0)); } else { return (_sel.createRange().parentElement()); } }, /** * native range 를 생성한다. * @returns {Object} - native range 객체 * @example * txSelection.createRange(); */ createRange: function() { var _sel = this.getSel(); return _sel.createRange(); }, /** * native text range 를 생성한다. * @returns {Object} - native text range 객체 * @example * txSelection.createTextRange(); */ createTextRange: function() { return this.doc.body.createTextRange(); }, /** * native range object를 리턴한다. * @returns {Object} - native range 객체 * @example * txSelection.getRange(); */ getRange: function(collapse){ var _sel = this.getSel(); var _type = _sel.type.toLowerCase(); if (_type == "none") { return _sel.createRange() ? _sel.createRange() : function(){ var _rng = this.doc.body.createTextRange(); _rng.collapse(_TRUE); _rng.select(); return _rng; }(); } if (collapse == _NULL) { return _sel.createRange(); } else { if (_type === "text") { var _rng = _sel.createRange(); _rng.collapse(collapse); _rng.select(); return _sel.createRange(); } else { if (_type === "control") { _sel.empty(); } return _sel.createRange(); } } }, /** * 선택된 영역의 collapse 여부(선택된 영역이 있는지 여부)를 리턴한다. * @returns {Boolean} - collapse 여부 * @example * txSelection.isCollapsed(); */ isCollapsed: function() { var _sel = this.getSel(); var _type = _sel.type.toLowerCase(); if(_type === "none") { return _TRUE; } else if(_type === "control") { return _TRUE; } else if(_type === "text") { var _rng = _sel.createRange(); return _rng.compareEndPoints('StartToEnd', _rng) == 0; } else { return _TRUE; } }, /** * 선택된 영역을 collapse 시킨다. * @param {Boolean} toStart - 위치, 시작 = true * @example * txSelection.collapse(true); */ collapse: function(toStart) { var _sel = this.getSel(); var _type = _sel.type.toLowerCase(); if(_type === "text") { var _rng = _sel.createRange(); _rng.collapse(toStart); _rng.select(); return _sel.createRange(); } else { if(_type === "control") { _sel.empty(); } return _sel.createRange(); } }, /** * 선택된 영역의 컨트롤 노드(img,object,hr,table,button)를 리턴한다. * @returns {Element} - 선택된 영역의 노드 * @example * txSelection.hasControl(); */ getControl: function() { var _sel = this.getSel(); var _type = _sel.type.toLowerCase(); if (_type === "control") { var _node = _sel.createRange().item(0); if($tom.kindOf(_node, '%control')) { return _node; } else { return _NULL; } } else { return _NULL; } }, /** * 선택된 영역이 컨트롤 노드인지 여부를 리턴한다. * @returns {Boolean} - 컨트롤 노드인지 여부 * @example * txSelection.hasControl(); */ hasControl: function() { var _sel = this.getSel(); var _type = _sel.type.toLowerCase(); if (_type === "control") { return _TRUE; } else { return _FALSE; } }, /** * 컨트롤 노드를 선택한다. * @param {Element} node - 컨트롤 노트 * @example * txSelection.selectControl(node); */ selectControl: function(node) { var _rng = this.doc.body.createControlRange(); _rng.add(node); _rng.select(); }, /** * 선택된 영역이 텍스트 데이터 영역의 어떤 위치인지를 리턴한다. * @returns {Number} - 텍스트 데이터 영역의 어떤 위치인지
* 텍스트의 처음 : $tom.__POSITION.__START_OF_TEXT : -1
* 텍스트의 중간 : $tom.__POSITION.__MIDDLE_OF_TEXT : 0
* 텍스트의 마지막 : $tom.__POSITION.__END_OF_TEXT : 1 * @example * txSelection.compareTextPos(); */ compareTextPos: function() { var _sel = this.getSel(); var _type = _sel.type.toLowerCase(); if(_type === "none") { var _rng = _sel.createRange(); var _rng2 = _rng.duplicate(); _rng2.moveToElementText(_rng.parentElement()); if ( _rng2.text.trim().replace(Trex.__WORD_JOINER_REGEXP, "").length == 0 ){ return $tom.__POSITION.__EMPTY_TEXT; } else if(_rng.compareEndPoints('StartToStart', _rng2) == 0) { return $tom.__POSITION.__START_OF_TEXT; } else if(_rng.compareEndPoints('EndToEnd', _rng2) == 0) { return $tom.__POSITION.__END_OF_TEXT; } else { return $tom.__POSITION.__MIDDLE_OF_TEXT; } } return $tom.__POSITION.__END_OF_TEXT; }, /** * @private * @reference http://msdn.microsoft.com/en-us/library/ms536745(VS.85).aspx StartToEnd - Move the start of the TextRange object to the end of the specified oTextRange parameter. StartToStart - Move the start of the TextRange object to the start of the specified oTextRange parameter. EndToStart - Move the end of the TextRange object to the start of the specified oTextRange parameter. EndToEnd - Move the end of the TextRange object to the end of the specified oTextRange parameter. */ transTextRange: function(rng, node, offset, toStart) { var _pntRng = this.createTextRange(); var _pntNode = this.win.span(Trex.__WORD_JOINER); $tom.insertAt(_pntNode, node); _pntRng.moveToElementText(_pntNode); $tom.remove(_pntNode); _pntRng.collapse(_TRUE); _pntRng.moveStart('character', offset); if (toStart) { rng.setEndPoint('StartToStart', _pntRng); } else { rng.setEndPoint('EndToEnd', _pntRng); } return rng; }, /** * @private * 특정 위치로 range의 시작위치를 지정한다. * @param {Object} rng - native range 객체 * @param {Element} node - 특정 부모 노드 * @param {Number} offset - 노드의 옵셋 * @example * txSelection.setStart(range, node, 1); */ setStart: function(rng, node, offset) { try { this.transTextRange(rng, node, offset, _TRUE); } catch (e) { console.log(e) } return rng; }, /** * @private * 특정 위치로 range의 끝위치를 지정한다. * @param {Object} rng - native range 객체 * @param {Element} node - 특정 부모 노드 * @param {Number} offset - 노드의 옵셋 * @example * txSelection.setEnd(range, node, 1); */ setEnd: function(rng, node, offset) { try { this.transTextRange(rng, node, offset, _FALSE); } catch (e) { console.log(e) } return rng; }, /** * 주어진 range를 선택한다. * @returns {Object} - native selection 객체 * @example * txSelection.selectRange(range); */ selectRange: function(rng) { rng.select(); } }; Trex.I.Selection.TridentStandard = { /** * 선택된 영역의 컨트롤 노드(img,object,hr,table,button)를 리턴한다. * @returns {Element} - 선택된 영역의 노드 * @example * txSelection.getControl(); */ getControl: function() { var _sel = this.getSel(); if(_sel.isCollapsed) { return null; } if ($tom.isElement(_sel.anchorNode)) { var _node = _sel.anchorNode.childNodes[_sel.anchorOffset]; if ($tom.kindOf(_node, '%control')) { return _node; } else { return null; } } //button var _prevNode = $tom.previous(_sel.focusNode); var _nextNode = $tom.next(_sel.anchorNode); if(_prevNode == _nextNode) { return $tom.first(_prevNode, '%control'); } else { return null; } }, /** * 컨트롤 노드를 선택한다. * @param {Element} node - 컨트롤 노트 * @example * txSelection.selectControl(node); */ selectControl: function(node) { var _rng = this.createRange(); _rng.selectNode(node); var _sel = this.getSel(); _sel.removeAllRanges(); _sel.addRange(_rng); } }; Trex.I.Selection.Gecko = { }; Trex.I.Selection.Webkit = { /** * 선택된 영역의 컨트롤 노드(img,object,hr,table,button)를 리턴한다. * @returns {Element} - 선택된 영역의 노드 * @example * txSelection.getControl(); */ getControl: function() { var _sel = this.getSel(); if(_sel.isCollapsed) { return _NULL; } if ($tom.isElement(_sel.anchorNode)) { var _node = _sel.anchorNode.childNodes[_sel.anchorOffset]; if ($tom.kindOf(_node, '%control')) { return _node; } else { return _NULL; } } //button var _prevNode = $tom.previous(_sel.focusNode); var _nextNode = $tom.next(_sel.anchorNode); if(_prevNode == _nextNode) { return $tom.first(_prevNode, '%control'); } else { return _NULL; } }, /** * 컨트롤 노드를 선택한다. * @param {Element} node - 컨트롤 노트 * @example * txSelection.selectControl(node); */ selectControl: function(node) { var _rng = this.createRange(); _rng.selectNode(node); var _sel = this.getSel(); _sel.removeAllRanges(); _sel.addRange(_rng); } }; Trex.I.Selection.Presto = { }; /** * native selection, range 객체를 wrapping 한 객체로
* browser에 따라 필요한 함수들을 mixin한다.
* 주로 Processor와 연관된 객체에서 호출하며,
* processor.getTxSel()를 통해서 txSelection를 얻어서 사용한다.
* native selection 과 구분짓기 위해서 txSelection로 명명한다. * * @class * @param {Object} processor - Processor 객체 */ Trex.Canvas.Selection = Trex.Class.create(/** @lends Trex.Canvas.Selection.prototype */{ /** @ignore */ $mixins: [ Trex.I.Selection.Standard, (($tx.msie_nonstd)? Trex.I.Selection.Trident: {}), (($tx.msie_std)? Trex.I.Selection.TridentStandard: {}), (($tx.gecko)? Trex.I.Selection.Gecko: {}), (($tx.webkit)? Trex.I.Selection.Webkit: {}), (($tx.presto)? Trex.I.Selection.Presto: {}) ], initialize: function(processor) { this.processor = processor; this.win = processor.win; this.doc = processor.doc; } });