Javascript.RU

Создать новую тему Ответ
 
Опции темы Искать в теме
  #1 (permalink)  
Старый 13.05.2009, 12:43
Аспирант
Отправить личное сообщение для Pattern Посмотреть профиль Найти все сообщения от Pattern
 
Регистрация: 13.05.2009
Сообщений: 37

Выделение одиночных тэгов
Привет!
Разрабатываю html-редактор. Чтобы изменить свойства какого либо объекта, понятное дело, требуется его выделение. Для "двойных тэгов" (типо <tag></tag>) проблем никаких не возникает, так как ссылку на объект легко получить через parentNode относительно позиции курсора. А вот с выделением одиночных тэгов (таких как <br/>, <hr/>, <input/>, <img/> и прочие) столкнулся с проблемой. Никак не могу сообразить, как узнать что пользователь выделил одиночный тэг?
Ниже привожу код, который отвечает за "сборку" дерева нодов относительно текущей позиции курсора. При каждом изменении позиции важно определить, что либо выделяется некая область редактируемого текста, либо просто происходит переменещие по нему.
При вызове getTreeNodes() опередяется с какого нода будет собираться дерево нодов, вплоть до <body>. Соответсвенно нужно знать:
- либо если выделена вся нода, сборка начнётся с неё
- либо сборка начнётся с родительского нода; тут же, если выделен какой то участок, он заносится в clipboard (для каких либо дальнейших операций).
Выделение одиночных тэгов в коде у меня определяются как range.commonAncestorContainer с длиной выделенного текста 0. То есть подобно тому, что курсор установлен где то в тексте без выделения.
Кто знает, как получить ссылку на выделенный объект, помогите разобраться?
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);
					}
				}
			}
		}
	}
};
Изображения:
Тип файла: jpg editor_img.jpg (16.3 Кб, 26 просмотров)
Ответить с цитированием
  #2 (permalink)  
Старый 13.05.2009, 19:16
Аспирант
Отправить личное сообщение для Pattern Посмотреть профиль Найти все сообщения от Pattern
 
Регистрация: 13.05.2009
Сообщений: 37

Прямо море ответов... Ну да ладно, ответ сам нашёл и как обычно, ларчик просто открывался. На самом деле, недоработка заключалась в том, что перед тем как получать текущую позицию курсора, нужно проверить, возможно пользователь выделил какой то объект. А отловить это можно перед определением 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> - методом клика по ним.
Ответить с цитированием
  #3 (permalink)  
Старый 26.05.2009, 11:33
Аспирант
Отправить личное сообщение для Pattern Посмотреть профиль Найти все сообщения от Pattern
 
Регистрация: 13.05.2009
Сообщений: 37

В продолжении истории, добрался теперь до злосчастного 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). Как сделать так, чтобы оно срабатывало при первом клике?
Ответить с цитированием
  #4 (permalink)  
Старый 26.05.2009, 13:28
Аспирант
Отправить личное сообщение для Pattern Посмотреть профиль Найти все сообщения от Pattern
 
Регистрация: 13.05.2009
Сообщений: 37

Мда... тихо сам с собою, я веду беседу =)
...
if(window.captureEvents){
    window.captureEvents(Event.CLICK|Event.MOUSEUP);
    window.onclick=window.onmouseup=handle;
}else
    document.onclick=window.onmouseup=handle;
...
Ответить с цитированием
  #5 (permalink)  
Старый 26.05.2009, 14:55
Аватар для x-yuri
Отправить личное сообщение для x-yuri Посмотреть профиль Найти все сообщения от x-yuri
 
Регистрация: 27.12.2008
Сообщений: 4,201

если хочешь, можешь со мной поговорить
Ответить с цитированием
  #6 (permalink)  
Старый 26.05.2009, 15:06
Отправить личное сообщение для Octane Посмотреть профиль Найти все сообщения от Octane  
Регистрация: 10.07.2008
Сообщений: 3,873

Опыт разработки 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 всей ссылки, как в этом случае определить, что выделение схлопнуто?

Последний раз редактировалось Octane, 26.05.2009 в 15:25.
Ответить с цитированием
  #7 (permalink)  
Старый 26.05.2009, 22:50
Аспирант
Отправить личное сообщение для Pattern Посмотреть профиль Найти все сообщения от Pattern
 
Регистрация: 13.05.2009
Сообщений: 37

Сообщение от x-yuri Посмотреть сообщение
если хочешь, можешь со мной поговорить
+1
Сообщение от Octane Посмотреть сообщение
Опыт разработки JavaScript WYSIWYG-редакторов далеко не у всех JavaScript-программистов есть
Да я (вроде) более менее частные примеры рассматривал.... А может просто мне уже это так кажется =)
Сообщение от Octane Посмотреть сообщение
Вот кстати, может сталкивались с такой проблемой:
Поигрался с твоим примером. Честно говоря в очередном шоке от 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). Честно подсмотрел в чужих примерах, сам бы фик когда догадался до этого!!!!
В ином случае мы вызываем функцию parentElement(), которая вернёт ссылку на объект родителя.
Ответить с цитированием
  #8 (permalink)  
Старый 26.05.2009, 23:29
Отправить личное сообщение для Octane Посмотреть профиль Найти все сообщения от Octane  
Регистрация: 10.07.2008
Сообщений: 3,873

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

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

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


Опции темы Искать в теме
Искать в теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Выделение строки <select> при выборе checkbox DarkMaster13 Общие вопросы Javascript 4 14.04.2014 03:30
Выделение строки <select> при выборе другого <select> simbion Элементы интерфейса 12 05.05.2009 03:43
кросс-браузерное выделение текста evgen28 Общие вопросы Javascript 4 28.04.2009 16:38
Выделение checkbox при условии атрибута... SashaBorandi jQuery 1 07.02.2009 14:18
Визуальное выделение части изображения Гость Общие вопросы Javascript 0 25.02.2008 19:44