blob: 4e56dd004c917a3609a00439640ed18621103507 [file] [log] [blame]
/*
* DOM utilites for elRTE
*
* @author: Dmitry Levashov (dio) dio@std42.ru
*/
(function($) {
elRTE.prototype.dom = function(rte) {
this.rte = rte;
var self = this;
this.regExp = {
textNodes : /^(A|ABBR|ACRONYM|ADDRESS|B|BDO|BIG|BLOCKQUOTE|CAPTION|CENTER|CITE|CODE|DD|DEL|DFN|DIV|DT|EM|FIELDSET|FONT|H[1-6]|I|INS|KBD|LABEL|LEGEND|LI|MARQUEE|NOBR|NOEMBED|P|PRE|Q|SAMP|SMALL|SPAN|STRIKE|STRONG|SUB|SUP|TD|TH|TT|VAR)$/,
textContainsNodes : /^(A|ABBR|ACRONYM|ADDRESS|B|BDO|BIG|BLOCKQUOTE|CAPTION|CENTER|CITE|CODE|DD|DEL|DFN|DIV|DL|DT|EM|FIELDSET|FONT|H[1-6]|I|INS|KBD|LABEL|LEGEND|LI|MARQUEE|NOBR|NOEMBED|OL|P|PRE|Q|SAMP|SMALL|SPAN|STRIKE|STRONG|SUB|SUP|TABLE|THEAD|TBODY|TFOOT|TD|TH|TR|TT|UL|VAR)$/,
block : /^(APPLET|BLOCKQUOTE|BR|CAPTION|CENTER|COL|COLGROUP|DD|DIV|DL|DT|H[1-6]|EMBED|FIELDSET|LI|MARQUEE|NOBR|OBJECT|OL|P|PRE|TABLE|THEAD|TBODY|TFOOT|TD|TH|TR|UL)$/,
selectionBlock : /^(APPLET|BLOCKQUOTE|BR|CAPTION|CENTER|COL|COLGROUP|DD|DIV|DL|DT|H[1-6]|EMBED|FIELDSET|LI|MARQUEE|NOBR|OBJECT|OL|P|PRE|TD|TH|TR|UL)$/,
header : /^H[1-6]$/,
formElement : /^(FORM|INPUT|HIDDEN|TEXTAREA|SELECT|BUTTON)$/
};
/********************************************************/
/* Утилиты */
/********************************************************/
/**
* Возвращает body редактируемого документа
*
* @return Element
**/
this.root = function() {
return this.rte.body;
}
this.create = function(t) {
return this.rte.doc.createElement(t);
}
/**
* Return node for bookmark with unique ID
*
* @return DOMElement
**/
this.createBookmark = function() {
var b = this.rte.doc.createElement('span');
b.id = 'elrte-bm-'+Math.random().toString().substr(2);
$(b).addClass('elrtebm elrte-protected');
return b;
}
/**
* Вовращает индекс элемента внутри родителя
*
* @param Element n нода
* @return integer
**/
this.indexOf = function(n) {
var ndx = 0;
n = $(n);
while ((n = n.prev()) && n.length) {
ndx++;
}
return ndx;
}
/**
* Вовращает значение аттрибута в нижнем регистре (ох уж этот IE)
*
* @param Element n нода
* @param String attr имя аттрибута
* @return string
**/
this.attr = function(n, attr) {
var v = '';
if (n.nodeType == 1) {
v = $(n).attr(attr);
if (v && attr != 'src' && attr != 'href' && attr != 'title' && attr != 'alt') {
v = v.toString().toLowerCase();
}
}
return v||'';
}
/**
* Вовращает ближайший общий контейнер для 2-х эл-тов
*
* @param Element n нода1
* @param Element n нода2
* @return Element
**/
this.findCommonAncestor = function(n1, n2) {
if (!n1 || !n2) {
return this.rte.log('dom.findCommonAncestor invalid arguments');
}
if (n1 == n2) {
return n1;
} else if (n1.nodeName == 'BODY' || n2.nodeName == 'BODY') {
return this.rte.doc.body;
}
var p1 = $(n1).parents(), p2 = $(n2).parents(), l = p2.length-1, c = p2[l];
for (var i = p1.length - 1; i >= 0; i--, l--){
if (p1[i] == p2[l]) {
c = p1[i];
} else {
break;
}
};
return c;
}
/**
* Вовращает TRUE, если нода пустая
* пустой считаем ноды:
* - текстовые эл-ты, содержащие пустую строку или тег br
* - текстовые ноды с пустой строкой
*
* @param DOMElement n нода
* @return bool
**/
this.isEmpty = function(n) {
if (n.nodeType == 1) {
return this.regExp.textNodes.test(n.nodeName) ? $.trim($(n).text()).length == 0 : false;
} else if (n.nodeType == 3) {
return /^(TABLE|THEAD|TFOOT|TBODY|TR|UL|OL|DL)$/.test(n.parentNode.nodeName)
|| n.nodeValue == ''
|| ($.trim(n.nodeValue).length== 0 && !(n.nextSibling && n.previousSibling && n.nextSibling.nodeType==1 && n.previousSibling.nodeType==1 && !this.regExp.block.test(n.nextSibling.nodeName) && !this.regExp.block.test(n.previousSibling.nodeName) ));
}
return true;
}
/********************************************************/
/* Перемещение по DOM */
/********************************************************/
/**
* Вовращает следующую соседнюю ноду (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
*
* @param DOMElement n нода
* @return DOMElement
**/
this.next = function(n) {
while (n.nextSibling && (n = n.nextSibling)) {
if (n.nodeType == 1 || (n.nodeType == 3 && !this.isEmpty(n))) {
return n;
}
}
return null;
}
/**
* Вовращает предыдующую соседнюю ноду (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
*
* @param DOMElement n нода
* @return DOMElement
**/
this.prev = function(n) {
while (n.previousSibling && (n = n.previousSibling)) {
if (n.nodeType == 1 || (n.nodeType ==3 && !this.isEmpty(n))) {
return n;
}
}
return null;
}
this.isPrev = function(n, prev) {
while ((n = this.prev(n))) {
if (n == prev) {
return true;
}
}
return false;
}
/**
* Вовращает все следующие соседнии ноды (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
*
* @param DOMElement n нода
* @return Array
**/
this.nextAll = function(n) {
var ret = [];
while ((n = this.next(n))) {
ret.push(n);
}
return ret;
}
/**
* Вовращает все предыдующие соседнии ноды (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
*
* @param DOMElement n нода
* @return Array
**/
this.prevAll = function(n) {
var ret = [];
while ((n = this.prev(n))) {
ret.push(n);
}
return ret;
}
/**
* Вовращает все следующие соседнии inline ноды (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
*
* @param DOMElement n нода
* @return Array
**/
this.toLineEnd = function(n) {
var ret = [];
while ((n = this.next(n)) && n.nodeName != 'BR' && n.nodeName != 'HR' && this.isInline(n)) {
ret.push(n);
}
return ret;
}
/**
* Вовращает все предыдующие соседнии inline ноды (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
*
* @param DOMElement n нода
* @return Array
**/
this.toLineStart = function(n) {
var ret = [];
while ((n = this.prev(n)) && n.nodeName != 'BR' && n.nodeName != 'HR' && this.isInline(n) ) {
ret.unshift(n);
}
return ret;
}
/**
* Вовращает TRUE, если нода - первый непустой эл-т внутри родителя
*
* @param Element n нода
* @return bool
**/
this.isFirstNotEmpty = function(n) {
while ((n = this.prev(n))) {
if (n.nodeType == 1 || (n.nodeType == 3 && $.trim(n.nodeValue)!='' ) ) {
return false;
}
}
return true;
}
/**
* Вовращает TRUE, если нода - последний непустой эл-т внутри родителя
*
* @param Element n нода
* @return bool
**/
this.isLastNotEmpty = function(n) {
while ((n = this.next(n))) {
if (!this.isEmpty(n)) {
return false;
}
}
return true;
}
/**
* Вовращает TRUE, если нода - единственный непустой эл-т внутри родителя
*
* @param DOMElement n нода
* @return bool
**/
this.isOnlyNotEmpty = function(n) {
return this.isFirstNotEmpty(n) && this.isLastNotEmpty(n);
}
/**
* Вовращает последний непустой дочерний эл-т ноды или FALSE
*
* @param Element n нода
* @return Element
**/
this.findLastNotEmpty = function(n) {
this.rte.log('findLastNotEmpty Who is here 0_o');
if (n.nodeType == 1 && (l = n.lastChild)) {
if (!this.isEmpty(l)) {
return l;
}
while (l.previousSibling && (l = l.previousSibling)) {
if (!this.isEmpty(l)) {
return l;
}
}
}
return false;
}
/**
* Возвращает TRUE, если нода "inline"
*
* @param DOMElement n нода
* @return bool
**/
this.isInline = function(n) {
if (n.nodeType == 3) {
return true;
} else if (n.nodeType == 1) {
n = $(n);
var d = n.css('display');
var f = n.css('float');
return d == 'inline' || d == 'inline-block' || f == 'left' || f == 'right';
}
return true;
}
/********************************************************/
/* Поиск элементов */
/********************************************************/
this.is = function(n, f) {
if (n && n.nodeName) {
if (typeof(f) == 'string') {
f = this.regExp[f]||/.?/;
}
if (f instanceof RegExp && n.nodeName) {
return f.test(n.nodeName);
} else if (typeof(f) == 'function') {
return f(n);
}
}
return false;
}
/**
* Вовращает элемент(ы) отвечающие условиям поиска
*
* @param DOMElement||Array n нода
* @param RegExp||String filter фильтр условия поиска (RegExp или имя ключа this.regExp или *)
* @return DOMElement||Array
**/
this.filter = function(n, filter) {
var ret = [], i;
if (!n.push) {
return this.is(n, filter) ? n : null;
}
for (i=0; i < n.length; i++) {
if (this.is(n[i], filter)) {
ret.push(n[i]);
}
};
return ret;
}
/**
* Вовращает массив родительских элементов, отвечающих условиям поиска
*
* @param DOMElement n нода, родителей, которой ищем
* @param RegExp||String filter фильтр условия поиска (RegExp или имя ключа this.regExp или *)
* @return Array
**/
this.parents = function(n, filter) {
var ret = [];
while (n && (n = n.parentNode) && n.nodeName != 'BODY' && n.nodeName != 'HTML') {
if (this.is(n, filter)) {
ret.push(n);
}
}
return ret;
}
/**
* Вовращает ближайший родительский эл-т, отвечающий условиям поиска
*
* @param DOMElement n нода, родителя, которой ищем
* @param RegExp||String f фильтр условия поиска (RegExp или имя ключа this.regExp или *)
* @return DOMElement
**/
this.parent = function(n, f) {
return this.parents(n, f)[0] || null;
}
/**
* Вовращает или саму ноду или ее ближайшего родителя, если выполняются условия sf для самой ноды или pf для родителя
*
* @param DOMElement n нода, родителя, которой ищем
* @param RegExp||String sf фильтр условия для самой ноды
* @param RegExp||String pf фильтр условия для родителя
* @return DOMElement
**/
this.selfOrParent = function(n, sf, pf) {
return this.is(n, sf) ? n : this.parent(n, pf||sf);
}
/**
* Вовращает родительскую ноду - ссылку
*
* @param Element n нода
* @return Element
**/
this.selfOrParentLink = function(n) {
n = this.selfOrParent(n, /^A$/);
return n && n.href ? n : null;
}
/**
* Вовращает TRUE, если нода - anchor
*
* @param Element n нода
* @return bool
**/
this.selfOrParentAnchor = function(n) {
n = this.selfOrParent(n, /^A$/);
return n && !n.href && n.name ? n : null;
}
/**
* Вовращает массив дочерних ссылок
*
* @param DOMElement n нода
* @return Array
**/
this.childLinks = function(n) {
var res = [];
$('a[href]', n).each(function() { res.push(this); });
return res;
}
this.selectionHas = function(f) {
var n = this.rte.selection.cloneContents(), i;
if (n && n.childNodes && n.childNodes.length) {
for (i=0; i < n.childNodes.length; i++) {
if (typeof(f) == 'function') {
if (f(n.childNodes[i])) {
return true;
}
} else if (n instanceof RegExp) {
if (f.test(n.childNodes[i].nodeName)) {
return true;
}
}
};
}
return false;
}
/********************************************************/
/* Изменения DOM */
/********************************************************/
/**
* Оборачивает одну ноду другой
*
* @param DOMElement n оборачиваемая нода
* @param DOMElement w нода обертка или имя тега
* @return DOMElement
**/
this.wrap = function(n, w) {
n = $.isArray(n) ? n : [n];
w = w.nodeName ? w : this.create(w);
if (n[0] && n[0].nodeType && n[0].parentNode) {
w = n[0].parentNode.insertBefore(w, n[0]);
$(n).each(function() {
if (this!=w) {
w.appendChild(this);
}
});
}
return w;
}
/**
* Replace node with its contents
*
* @param DOMElement n node
* @return void
**/
this.unwrap = function(n) {
if (n && n.parentNode) {
while (n.firstChild) {
n.parentNode.insertBefore(n.firstChild, n);
}
n.parentNode.removeChild(n);
}
}
/**
* Оборачивает все содержимое ноды
*
* @param DOMElement n оборачиваемая нода
* @param DOMElement w нода обертка или имя тега
* @return DOMElement
**/
this.wrapContents = function(n, w) {
w = w.nodeName ? w : this.create(w);
for (var i=0; i < n.childNodes.length; i++) {
w.appendChild(n.childNodes[i]);
};
n.appendChild(w);
return w;
}
this.cleanNode = function(n) {
if (n.nodeType != 1) {
return;
}
if (/^(P|LI)$/.test(n.nodeName) && (l = this.findLastNotEmpty(n)) && l.nodeName == 'BR') {
$(l).remove();
}
$n = $(n);
$n.children().each(function() {
this.cleanNode(this);
});
if (n.nodeName != 'BODY' && !/^(TABLE|TR|TD)$/.test(n) && this.isEmpty(n)) {
return $n.remove();
}
if ($n.attr('style') === '') {
$n.removeAttr('style');
}
if (this.rte.browser.safari && $n.hasClass('Apple-span')) {
$n.removeClass('Apple-span');
}
if (n.nodeName == 'SPAN' && !$n.attr('style') && !$n.attr('class') && !$n.attr('id')) {
$n.replaceWith($n.html());
}
}
this.cleanChildNodes = function(n) {
var cmd = this.cleanNode;
$(n).children().each(function() { cmd(this); });
}
/********************************************************/
/* Таблицы */
/********************************************************/
this.tableMatrix = function(n) {
var mx = [];
if (n && n.nodeName == 'TABLE') {
var max = 0;
function _pos(r) {
for (var i=0; i<=max; i++) {
if (!mx[r][i]) {
return i;
}
};
}
$(n).find('tr').each(function(r) {
if (!$.isArray(mx[r])) {
mx[r] = [];
}
$(this).children('td,th').each(function() {
var w = parseInt($(this).attr('colspan')||1);
var h = parseInt($(this).attr('rowspan')||1);
var i = _pos(r);
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
var _y = r+y;
if (!$.isArray(mx[_y])) {
mx[_y] = [];
}
var d = x==0 && y==0 ? this : (y==0 ? x : "-");
mx[_y][i+x] = d;
}
};
max= Math.max(max, mx[r].length);
});
});
}
return mx;
}
this.indexesOfCell = function(n, tbm) {
for (var rnum=0; rnum < tbm.length; rnum++) {
for (var cnum=0; cnum < tbm[rnum].length; cnum++) {
if (tbm[rnum][cnum] == n) {
return [rnum, cnum];
}
};
};
}
this.fixTable = function(n) {
if (n && n.nodeName == 'TABLE') {
var tb = $(n);
//tb.find('tr:empty').remove();
var mx = this.tableMatrix(n);
var x = 0;
$.each(mx, function() {
x = Math.max(x, this.length);
});
if (x==0) {
return tb.remove();
}
// for (var i=0; i<mx.length; i++) {
// this.rte.log(mx[i]);
// }
for (var r=0; r<mx.length; r++) {
var l = mx[r].length;
//this.rte.log(r+' : '+l)
if (l==0) {
//this.rte.log('remove: '+tb.find('tr').eq(r))
tb.find('tr').eq(r).remove();
// tb.find('tr').eq(r).append('<td>remove</td>')
} else if (l<x) {
var cnt = x-l;
var row = tb.find('tr').eq(r);
for (i=0; i<cnt; i++) {
row.append('<td>&nbsp;</td>');
}
}
}
}
}
this.tableColumn = function(n, ext, fix) {
n = this.selfOrParent(n, /^TD|TH$/);
var tb = this.selfOrParent(n, /^TABLE$/);
ret = [];
info = {offset : [], delta : []};
if (n && tb) {
fix && this.fixTable(tb);
var mx = this.tableMatrix(tb);
var _s = false;
var x;
for (var r=0; r<mx.length; r++) {
for (var _x=0; _x<mx[r].length; _x++) {
if (mx[r][_x] == n) {
x = _x;
_s = true;
break;
}
}
if (_s) {
break;
}
}
// this.rte.log('matrix');
// for (var i=0; i<mx.length; i++) {
// this.rte.log(mx[i]);
// }
if (x>=0) {
for(var r=0; r<mx.length; r++) {
var tmp = mx[r][x]||null;
if (tmp) {
if (tmp.nodeName) {
ret.push(tmp);
if (ext) {
info.delta.push(0);
info.offset.push(x);
}
} else {
var d = parseInt(tmp);
if (!isNaN(d) && mx[r][x-d] && mx[r][x-d].nodeName) {
ret.push(mx[r][x-d]);
if (ext) {
info.delta.push(d);
info.offset.push(x);
}
}
}
}
}
}
}
return !ext ? ret : {column : ret, info : info};
}
}
})(jQuery);