Javascript-форум (https://javascript.ru/forum/)
-   Events/DOM/Window (https://javascript.ru/forum/events/)
-   -   Выделение одиночных тэгов (https://javascript.ru/forum/events/3663-vydelenie-odinochnykh-tehgov.html)

Pattern 13.05.2009 12:43

Выделение одиночных тэгов
 
Вложений: 1
Привет!
Разрабатываю html-редактор. Чтобы изменить свойства какого либо объекта, понятное дело, требуется его выделение. Для "двойных тэгов" (типо <tag></tag>) проблем никаких не возникает, так как ссылку на объект легко получить через parentNode относительно позиции курсора. А вот с выделением одиночных тэгов (таких как <br/>, <hr/>, <input/>, <img/> и прочие) столкнулся с проблемой. Никак не могу сообразить, как узнать что пользователь выделил одиночный тэг?
Ниже привожу код, который отвечает за "сборку" дерева нодов относительно текущей позиции курсора. При каждом изменении позиции важно определить, что либо выделяется некая область редактируемого текста, либо просто происходит переменещие по нему.
При вызове getTreeNodes() опередяется с какого нода будет собираться дерево нодов, вплоть до <body>. Соответсвенно нужно знать:
- либо если выделена вся нода, сборка начнётся с неё
- либо сборка начнётся с родительского нода; тут же, если выделен какой то участок, он заносится в clipboard (для каких либо дальнейших операций).
Выделение одиночных тэгов в коде у меня определяются как range.commonAncestorContainer с длиной выделенного текста 0. То есть подобно тому, что курсор установлен где то в тексте без выделения.
Кто знает, как получить ссылку на выделенный объект, помогите разобраться?:help:
jsCore.storage={
	parentObject: null,
	range: null,
	buffer: '',
	clipboard: null,
	savedCopy: null,
	tree: null,
/**
 * Функция получения выделенного объекта
 * срабатывает при любом изменении позиции текстового курсора
 */
	get: function(){
//объект источника данных
		obj=arguments[1]||richedit||null;
//событие, вызвавшее функцию
		e=arguments[0]||window.event
//		jsCore.debug(e,17,'storage.js');
//		e=e||window.event||null;
//		var target=(e!=null && e.target!=null ? e.target : e.srcElement);
		var range=null;
//Gecko, Opera
		if(jsCore.browser.isGecko() || jsCore.browser.isOpera()){
//Если выделение произвелось в редактируемой области
			if(obj.contentWindow.getSelection){
//Выделенная область
				var buffer=obj.contentWindow.getSelection();
//Сохраняем объект range
				range=buffer.getRangeAt(0);
//Если ничего не выделено
				if(range.offsetStart==range.offsetEnd && range.startContainer==range.endContainer && buffer.toString().length==0){
//Для дальнейшей работы потребуется ссылка на выделенный объёкт
					jsCore.storage.range=range;
//Если выделена какая то область
				}else{
//Создаем объект, с помощью которого можно получить HTML содержимое выделенного объекта
					jsCore.storage.clipboard=jsCore.dom.create.tag('span');
//В clipboard сохраняем HTML-содержимое выделенного объекта
					jsCore.storage.clipboard.appendChild(range.cloneContents());
//В buffer сохраняем текст выделенного объекта
					jsCore.storage.buffer=buffer;
					jsCore.storage.range=range;
				}
				return true;
			}
//IE
		}else if(jsCore.browser.isIE()){
//Если выделение произвелось в редактируемой области
			if(re_doc.selection){
				range=re_doc.selection.createRange();
				jsCore.storage.buffer=range.text;
				jsCore.storage.clipboard=range.htmlText;
				return true;
			}else{
				jsCore.storage.clear();
				return false;
			}
		}else{
			jsCore.storage.clear();
			return false;
		}
		return true;
	},
	getTreeNodes: function(){
		jsCore.storage.get(richedit);
		if(jsCore.storage.range){
			var o=null;
			jsCore.debug(jsCore.storage.range,67,'storage.js::range');
			if(jsCore.storage.clipboard
				&& jsCore.storage.clipboard.childNodes
				&& jsCore.storage.clipboard.childNodes.length==1
				&& jsCore.storage.clipboard.childNodes[0].nodeType!=3
				&& jsCore.storage.clipboard.childNodes[0].innerHTML==jsCore.storage.buffer){
					o=jsCore.storage.range.startContainer.childNodes[0];
					jsCore.debug(o,74,'storage.js::currentNode');
			}else{
				if(jsCore.storage.range.commonAncestorContainer.nodeType!=3){
					o=jsCore.storage.range.commonAncestorContainer;
					jsCore.debug(o,78,'storage.js::commonAncestorContainer');
				}else{
					o=jsCore.storage.range.commonAncestorContainer.parentNode;
					jsCore.debug(o,81,'storage.js::parentNode');
				}
			}
			if(o){
				if(o.tagName==null){
					jsCore.debug(o,86,'storage.js::-=[ !!! BUG !!! ]=-');
				}else{
					if(o.tagName.toLowerCase()!='html'){
						jsCore.storage.tree=new Array();
						while(o && o.tagName.toLowerCase()!='body'){
							if(o.tagName){
								jsCore.storage.tree.push(o);
								o=o.parentNode;
							}else{
								o=o.parentNode;
							}
							if(o==null)
								break;
							if(o.tagName==null)
								o=o.parentNode;
						}
						if(o)
							jsCore.storage.tree.push(o);
					}
				}
			}
		}
	}
};

Pattern 13.05.2009 19:16

Прямо море ответов... Ну да ладно, ответ сам нашёл и как обычно, ларчик просто открывался. На самом деле, недоработка заключалась в том, что перед тем как получать текущую позицию курсора, нужно проверить, возможно пользователь выделил какой то объект. А отловить это можно перед определением range через getSelection().anchorNode + getSelection().anchorOffset:
var buffer=obj.contentWindow.getSelection();
//Если выделен какой то объект...
				if(buffer.anchorNode.childNodes[buffer.anchorOffset]){
//...сохраняем его в хранилище
					jsCore.storage.parentObject=buffer.anchorNode.childNodes[buffer.anchorOffset];
				}
//Сохраняем объект range
				range=buffer.getRangeAt(0);
/*** ну и далее по коду с минимальными изменениями ***/

Собственно в хранилище объект попадёт только тогда, когда курсор "попал" непосредственно на объект. Методом перемещения курсора можно получить <br/>, а <input>, <img> - методом клика по ним.

Pattern 26.05.2009 11:33

В продолжении истории, добрался теперь до злосчастного IE.
<html>
<head>
<title>Test page</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<script language="javascript">
if(window.captureEvents){
	window.captureEvents(Event.CLICK);
	window.onclick=handle;
}else
	document.onclick=handle;
function handle(e){
	var el=(typeof event!=='undefined')?event.srcElement:e.target;
	alert(el.tagName);
}
</script>
</head>
<body contenteditable="true">
<div>Any text into tag div</div>
<br />
<img src="images/misc/float_above.gif" />
<hr />
<table width="100%" border="1">
<tr>
	<td>Cell 1</td>
	<td>Cell 2</td>
	<td>Cell 3</td>
</tr>
</table>
</body>
</html>

Данный код в браузере IE (тестировал в 6 и 7 версиях) событие onclick на тэгах <IMG /> и <HR /> срабатывает только при повторном клике на элементах (именно повторный onclick, а не ondblclick). Как сделать так, чтобы оно срабатывало при первом клике?

Pattern 26.05.2009 13:28

Мда... тихо сам с собою, я веду беседу =)
...
if(window.captureEvents){
    window.captureEvents(Event.CLICK|Event.MOUSEUP);
    window.onclick=window.onmouseup=handle;
}else
    document.onclick=window.onmouseup=handle;
...

x-yuri 26.05.2009 14:55

если хочешь, можешь со мной поговорить ;)

Octane 26.05.2009 15:06

Опыт разработки JavaScript WYSIWYG-редакторов далеко не у всех JavaScript-программистов есть :)

Вот кстати, может сталкивались с такой проблемой:
<html>
<head>
<script>
document.onmouseup = function() {
	alert(document.selection.createRange().htmlText);
};
</script>
</head>
<body>
	<ul>
		<li><a href="…">link</a></li>
	</ul>
</body>
</html>

Если поставить курсор перед ссылкой или выделить ссылку, всеравно в TextRange.htmlText будет outerHTML всей ссылки, как в этом случае определить, что выделение схлопнуто?

Pattern 26.05.2009 22:50

Цитата:

Сообщение от x-yuri (Сообщение 20119)
если хочешь, можешь со мной поговорить ;)

:lol: +1
Цитата:

Сообщение от Octane (Сообщение 20121)
Опыт разработки JavaScript WYSIWYG-редакторов далеко не у всех JavaScript-программистов есть :)

Да я (вроде) более менее частные примеры рассматривал.... А может просто мне уже это так кажется =)
Цитата:

Сообщение от Octane (Сообщение 20121)
Вот кстати, может сталкивались с такой проблемой:

Поигрался с твоим примером. Честно говоря в очередном шоке от IE. Правда, честно говоря, что createRange().text, что createRange().htmlText - абсолютно не нужные фичи. Во всяком случае у меня в редакторе они чисто для видимости присутствуют. Если есть createRange(), соответственно будет работать execCommand. А для всего остального нужно получать ссылки на объекты, с которыми хочешь произвести операции. Вот код чисто для примера:
<html>
<head>
<script language="javascript">
var init=function(){
  document.onmouseup=function(){
    var range=document.selection.createRange();
    var buffer=range.text;
    var curEl=(range.length>0)?range.item(0):range.parentElement();
    var curTag=curEl.tagName;
    var str='';
    if(buffer)
      str+='Выделен текст: '+buffer+"\n";
    if(curTag)
      str+='Текущий тэг: '+curTag+"\n";
    if(range.length>0)
      str+='Одиночный тэг';
    else
      str+='Содержимое тэга: '+curEl.innerHTML;
    alert(str);
  };
}
</script>
</head>
<body contenteditable="true" onload="init()">
<img src="mypicture.png" />
<hr />
<ul>
  <li><a href="#">link</a></li>
</ul>
<table>
<tr>
  <td>Cell 1</td>
  <td>Cell 2</td>
  <td>Cell 3</td>
</tr>
</table>
</body>
</html>

Вся задача разрешается в этом маленьком участке кода
(range.length>0) ? range.item(0) : range.parentElement();

Собственно, если выделен одиночный тэг, в range появится элемент, доступ к которому можно получить через range.item(0). Честно подсмотрел в чужих примерах, сам бы фик когда догадался до этого!!!! :blink:
В ином случае мы вызываем функцию parentElement(), которая вернёт ссылку на объект родителя.

Octane 26.05.2009 23:29

Я спрашивал, как определить, схлопнуто выделение или нет, если курсор стоит перед ссылкой, т.е. мне нужно было реализовать аналог свойства Range.collapsed.
Оказывается, если курсор перед ссылкой, то в TextRange.htmlText будет HTML-код ссылки, но без текста:
<a …></a>

ну а у выделенной ссылки естественно все на месте:
<a …>текст ссылки</a>

Решение:
1)дублируем TextRange;
2)коллапсируем копию в начало;
3)если конец выделение совпал с началом копии (range1.compareEndPoints("EndToStart", range2) == 0), значит выделение схлопнуто (collapsed = true).


Часовой пояс GMT +3, время: 11:04.