1.editor的iframe window的keydown事件绑定
由于htmleditor本身提供的specialkey event不给力,所以自己手动在init时增加更加精确的keydown事件来弥补
需要注意的是:chrome的事件必须绑定在body上,否则ENTER这种特殊的键无法触发
var win = Ext.isIE ? ed.getDoc() : ed.getWin();
...
Ext.EventManager.on(!Ext.isWebKit ? win : win.document.body,
'keydown', function(e) {
if (e.isSpecialKey())
this.fireEvent('_specialkey', this, e)
}, ed);
2.chrome下htmleditor回车会有问题(光标位置不正确),发现源代码中extjs官网是加了2个br,结果是不对的,修正方法是在fixKeys方法中修改webkit部分,回车时插入\n(chrome会自动变成\n<br>),getvalue时干掉所有\n<br>,比较纠结的方法,但是能解决问题,代码 如下
fixKeys : function() {
if (Ext.isIE) {
...
} else if (Ext.isOpera) {
...
} else if (Ext.isWebKit) {
return function(e) {
var k = e.getKey();
if (k == e.TAB) {
e.stopEvent();
this.execCmd('InsertText', '\t');
this.deferFocus();
} else if (k == e.ENTER) {
e.stopEvent();
// fix chrome ENTER double pressed bug
this.execCmd('InsertText', '\n');
this.deferFocus();
}
};
}
}(),
getValue : function() {
var ret = FloatHtmlEditor.superclass.getValue.apply(this, arguments);
if (ret) {
// fix edit grid panel compare startvalue & nowvalue
// fix '\n' patch for webkit(chrome) when ENTER pressed
ret = ret.replace(/^( |<br>|\s)*|( |<br>|\s)*$/ig, '')
ret = ret.replace(/<div>(.+?)<\/div>/ig,'<br>$1')
}
if (!ret) {
ret = '';
}
return ret;
},
FloatHtmlEditor 是一个用于嵌入在grid中的editor,主要是修改了edit的bar,能够浮动出来,节约grid中的编辑区域
其他扩展特性:
* copy/paste 简化所有copy内容为纯文本,包括word/excel等复杂的格式
* 能够自适应文字大小,自动扩展编辑区域
* 修正多个平台下的focus的问题(htmleditor默认的focus和其他field有区别,不能定位的文字末尾)
完整源码:
var FloatHtmlEditor = Ext.extend(Ext.form.HtmlEditor, {
fixKeys : function() {
if (Ext.isIE) {
return function(e) {
var k = e.getKey(), doc = this.getDoc(), r;
if (k == e.TAB) {
e.stopEvent();
r = doc.selection.createRange();
if (r) {
r.collapse(true);
r.pasteHTML(' ');
this.deferFocus();
}
} else if (k == e.ENTER) {
r = doc.selection.createRange();
if (r) {
var target = r.parentElement();
if (!target || target.tagName.toLowerCase() != 'li') {
e.stopEvent();
r.pasteHTML('<br />');
r.collapse(false);
r.select();
}
}
}
};
} else if (Ext.isOpera) {
return function(e) {
var k = e.getKey();
if (k == e.TAB) {
e.stopEvent();
this.win.focus();
this.execCmd('InsertHTML', ' ');
this.deferFocus();
}
};
} else if (Ext.isWebKit) {
return function(e) {
var k = e.getKey();
if (k == e.TAB) {
e.stopEvent();
this.execCmd('InsertText', '\t');
this.deferFocus();
} else if (k == e.ENTER) {
e.stopEvent();
// fix chrome ENTER double pressed bug
this.execCmd('InsertHTML', '\n<br>');
this.deferFocus();
}
};
}
}(),
_measureDiv : null,
_getFrame : function() {
if (!this._frm)
this._frm = this.getEl().parent().query('iframe')[0];
return this._frm
},
_adjustHeight : function() {
var t = Ext.fly(this._getFrame());
var div = this._measureDiv
if (!div) {
div = this._measureDiv = this.__measureDiv = document
.createElement('div');
div.style.position = 'absolute';
// div.style.border = '1px solid red'
div.style.top = '-1px';
div.style.left = '-10000px'
div.style.whiteSpace = 'normal';
div.style.wordWrap = 'break-word';
div.style.lineHeight = '15px'
document.body.appendChild(div)
}
if (t.dom.contentWindow.document.body.innerHTML == '') {
t.setHeight(this._min_h);
return
}
if (div.innerHTML == t.dom.contentWindow.document.body.innerHTML)
return;
var width = parseInt(t.getWidth());
Ext.isIE ? width -= 25 : width -= 7
div.style.width = width + 'px'
div.innerHTML = t.dom.contentWindow.document.body.innerHTML;
this._adjust_h = div.offsetHeight
if (this._adjust_h < this._min_h) {
this._adjust_h = this._min_h;
}
t.setHeight(this._adjust_h);
},
onEditorEvent : function(e) {
if (this.onSpecialKey) {
this.onSpecialKey(e);
}
return FloatHtmlEditor.superclass.onEditorEvent.apply(this, arguments)
},
getValue : function() {
var ret = FloatHtmlEditor.superclass.getValue.apply(this, arguments);
if (ret) {
// fix edit grid panel compare startvalue & nowvalue
// fix '\n' patch for webkit(chrome) when ENTER pressed
ret = ret.replace(/^( |<br>|\s)*|( |<br>|\s)*$/ig, '')
ret = ret.replace(/<div>(.+?)<\/div>/ig,'<br>$1')
}
if (!ret) {
ret = '';
}
return ret;
},
markTeamMember : function() {
this.teamCmp = true
},
initComponent : function() {
this.valueEditToolbarId = Ext.id()
this.onBlur = Ext.form.Field.prototype.onBlur;
FloatHtmlEditor.superclass.initComponent.call(this);
this.addEvents('_specialkey')
this.on('afterrender', function() {
this.getToolbar().hide();
}, this)
this.on('show', function() {
this._adjust_h = 0;
this._adjustHeight.defer(100, this);
this.focus()
}, this)
this.on('initialize', function(ed) {
var toolbarWin = this.toolbarWin = new Ext.Window({
html : '<div id="' + this.valueEditToolbarId
+ '" class=""></div>',
height : 255,
width : 50,
closeAction : 'hide',
resizable : false,
iconCls : 'icon-toolbox'
})
toolbarWin.show();
var xy = this.getHoverTarget().getEl().getXY();
toolbarWin.setPagePosition(xy[0] + this.getHoverTarget().getWidth()
- toolbarWin.getWidth(), xy[1]);
var el = this.getToolbar().getEl();
var appentToEl = Ext.getDom(this.valueEditToolbarId);
appentToEl.className = el.dom.parentNode.className;
el.appendTo(appentToEl);
this.getToolbar().show();
var tbarRawRow = el.query('.x-toolbar-left-row')[0];
var btnNodes = Ext.Element.fly(tbarRawRow).query('.x-toolbar-cell');
Ext.each(btnNodes, function(btnNode) {
var btnEl = Ext.Element.fly(btnNode);
if (btnEl.query('.xtb-sep').length) {
btnEl.remove();
} else {
var newRow = Ext.Element.fly(tbarRawRow)
.insertSibling({
tag : 'tr',
cls : 'x-toolbar-left-row'
});
newRow.appendChild(btnNode);
var nel = Ext.Element.fly(btnNode);
nel.query('button')[0].setAttribute('unselectable',
'on')
nel.on('click', function() {
this.fireEvent('styleBtnClick')
}, this)
}
}, this)
toolbarWin.hide();
if (!Ext.isIE) {
toolbarWin.on('move', function() {
ed.markTeamMember()
})
}
this.getEl().findParent("div", 2, true).query('.x-html-editor-tb')[0].style.display = 'none'
Ext.EventManager.on(ed.getWin(), 'blur', function() {
if (this.adjustTimer) {
clearInterval(this.adjustTimer);
delete this.adjustTimer
}
if (this._measureDiv) {
this._measureDiv.innerHTML = ''
}
this._adjust_h = 0;
(function () {
var teamCmp = ed.teamCmp;
teamCmp ? delete ed.teamCmp : ed.onBlur.apply(ed,
arguments)
}).defer(10, ed, arguments);
}, ed);
ed.getWin().document.body.style.overflow = 'hidden'
ed.getWin().document.body.style.whiteSpace = 'normal';
ed.getWin().document.body.style.wordWrap = 'break-word';
ed.getWin().document.body.style.lineHeight = '15px';
Ext.EventManager.on(ed.getWin(), 'focus', function() {
if (this.teamCmp)
delete this.teamCmp
}.dg(this), ed);
var win = Ext.isIE ? ed.getDoc() : ed.getWin()
Ext.EventManager.on(Ext.isIE ? ed.getDoc().documentElement : ed
.getWin(), 'paste', function(e) {
var html = ed.getDoc().body.innerHTML;
if (html.length >= 4
&& html.substring(html.length - 4) == '<br>') {
ed._beforePasteHTMLPos = html.length - 4;
} else {
ed._beforePasteHTMLPos = html.length
}
})
// fix chrome keydown problem
Ext.EventManager.on(!Ext.isWebKit ? win : win.document.body,
'keydown', function(e) {
if (!this.adjustTimer) {
this.adjustTimer = setInterval(function() {
this._adjustHeight();
}.dg(this), 200);
}
if (e.isSpecialKey())
this.fireEvent('_specialkey', this, e)
}, ed);
// fix IE first range select
this.moveCursorToEnd()
}, this)
},
// public >>>
getHoverTarget : Ext.emptyFn,
toolbarWin : null,
valueEditToolbarId : null,
// public <<<
_adjust_h : 0,
_min_h : 34,
setSize : function(w, h) {
if (typeof w == 'object') {
h = w.height
w = w.width
}
if (this._adjust_h == 0)
this._min_h = h - 4;
FloatHtmlEditor.superclass.setSize.apply(this, arguments);
var t = Ext.Element.fly(this.getEl().parent().query('iframe')[0]);
t.setHeight(this._adjust_h ? this._adjust_h : this._min_h);
},
focus : function(st, delay) {
var t = this.getWin();
if (this._focus_delay)
clearTimeout(this._focus_delay)
this._focus_delay = window.setTimeout(function() {
t.focus();
}, 10);
},
moveCursorToEnd : function() {
this.focusPatch()
},
focusPatch : function() {
var ed = this;
if (!ed.win)
return
var doc = ed.getDoc();
ed.win.focus();
if (Ext.isIE) {
var r = doc.selection.createRange();
if (r) {
r.moveStart('character', 1000);
r.collapse(true);
r.select();
}
} else {
var contentDoc = doc;
var range = contentDoc.createRange();
var lastNode = contentDoc.body.childNodes[contentDoc.body.childNodes.length
- 1];
var end = 0;
var selection = ed.getWin().getSelection();
range.setStart(contentDoc.body.firstChild, 0);
if (lastNode.nodeType == 3) {
end = lastNode.textContent.length;
range.setEnd(lastNode, end);
} else {
range.setEndAfter(lastNode)
}
selection.removeAllRanges();
// chorme need the range collapsed before add to selection
range.collapse(false);
selection.addRange(range);
}
},
showTBWin : function() {
var tbwin = this.toolbarWin
if (tbwin) {
var a = tbwin.toFront
tbwin.toFront = function() {
}
tbwin.show()
tbwin.toFront = a
}
},
// clean all tags
syncValue : function() {
if (this.initialized) {
var bd = this.getEditorBody();
var len = bd.innerHTML.length;
FloatHtmlEditor.superclass.syncValue.call(this);
if (this._beforePasteHTMLPos != null) {
this.syncValue1();
var pastehtml = bd.innerHTML
// .substring(this._beforePasteHTMLPos);
// replace all tags,only plain text from clipboard
var adjustpastehtml = pastehtml
// trim spaces between tags
// replace \n for excel paste
.replace(/\s+/ig, '')
// replace tag except for <br>
.replace(/<(?!br).*?>/ig, '')
if (pastehtml != adjustpastehtml) {
bd.innerHTML = adjustpastehtml;
}
delete this._beforePasteHTMLPos;
}
}
},
destroy : function() {
if (this._measureDiv) {
this._measureDiv.parentNode.removeChild(this._measureDiv);
delete this._measureDiv
}
if (this.adjustTimer) {
clearInterval(this.adjustTimer)
delete this.adjustTimer
}
FloatHtmlEditor.superclass.destroy.call(this)
},
enableAlignments : false,
enableFont : false,
enableLinks : false,
enableSourceEdit : true,
// 清理excel,word copy paste的内容,code from HtmlLintEditor
dirtyHtmlTags : [
// http://stackoverflow.com/questions/2875027/clean-microsoft-word-pasted-text-using-javascript
// http://stackoverflow.com/questions/1068280/javascript-regex-multiline-flag-doesnt-work
{
regex : /<!--[\s\S]*?-->/gi,
replaceVal : ""
},
// http://www.1stclassmedia.co.uk/developers/clean-ms-word-formatting.php
{
regex : /<\\?\?xml[^>]*>/gi,
replaceVal : ""
}, {
regex : /<\/?\w+:[^>]*>/gi,
replaceVal : ""
}, // e.g. <o:p...
{
regex : /\s*MSO[-:][^;"']*/gi,
replaceVal : ""
}, {
regex : /\s*MARGIN[-:][^;"']*/gi,
replaceVal : ""
}, {
regex : /\s*PAGE[-:][^;"']*/gi,
replaceVal : ""
}, {
regex : /\s*TAB[-:][^;"']*/gi,
replaceVal : ""
}, {
regex : /\s*LINE[-:][^;"']*/gi,
replaceVal : ""
}, {
regex : /\s*FONT-SIZE[^;"']*/gi,
replaceVal : ""
}, {
regex : /\s*LANG=(["'])[^"']*?\1/gi,
replaceVal : ""
},
// keep new line
{
regex : /<(P)[^>]*>([\s\S]*?)<\/\1>/gi,
replaceVal : "$2<br>"
}, {
regex : /<(H\d)[^>]*>([\s\S]*?)<\/\1>/gi,
replaceVal : "$2"
},
{
regex : /\s*\w+=(["'])(( |\s|;)*|\s*;+[^"']*?|[^"']*?;{2,})\1/gi,
replaceVal : ""
}, {
regex : /<span[^>]*>( |\s)*<\/span>/gi,
replaceVal : ""
},
// {regex: /<([^\s>]+)[^>]*>( |\s)*<\/\1>/gi, replaceVal: ""},
// http://www.codinghorror.com/blog/2006/01/cleaning-words-nasty-html.html
{
regex : /<(\/?title|\/?meta|\/?style|\/?st\d|\/?head|\/?html|\/?body|\/?link|!\[)[^>]*?>/gi,
replaceVal : ""
}, {
regex : /(\n(\r)?){2,}/gi,
replaceVal : ""
}],
syncValue1 : function() {
if (this.initialized) {
var bd = this.getEditorBody();
var html = bd.innerHTML;
if (this.hasDirtyHtmlTags(html)) {
// Note: the selection will be lost...
bd.innerHTML = this.cleanHtml(html);
if (Ext.isGecko) {
// Gecko hack, see:
// https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
this.setDesignMode(false); // toggle off first
this.setDesignMode(true);
}
}
}
},
hasDirtyHtmlTags : function(html) {
if (!html)
return;
var hasDirtyHtmlTags = false;
Ext.each(this.dirtyHtmlTags, function(tag, idx) {
return !(hasDirtyHtmlTags = html.match(tag.regex));
});
return hasDirtyHtmlTags;
},
cleanHtml : function(html) {
if (!html)
return;
Ext.each(this.dirtyHtmlTags, function(tag, idx) {
html = html.replace(tag.regex, tag.replaceVal);
});
// http://www.tim-jarrett.com/labs_javascript_scrub_word.php
html = html.replace(new RegExp(String.fromCharCode(8220), 'gi'), '"'); // “
html = html.replace(new RegExp(String.fromCharCode(8221), 'gi'), '"'); // ”
html = html.replace(new RegExp(String.fromCharCode(8216), 'gi'), "'"); // ‘
html = html.replace(new RegExp(String.fromCharCode(8217), 'gi'), "'"); // ‘
html = html.replace(new RegExp(String.fromCharCode(8211), 'gi'), "-"); // –
html = html.replace(new RegExp(String.fromCharCode(8212), 'gi'), "--"); // —
html = html.replace(new RegExp(String.fromCharCode(189), 'gi'), "1/2"); // ½
html = html.replace(new RegExp(String.fromCharCode(188), 'gi'), "1/4"); // ¼
html = html.replace(new RegExp(String.fromCharCode(190), 'gi'), "3/4"); // ¾
html = html.replace(new RegExp(String.fromCharCode(169), 'gi'), "(C)"); // ©
html = html.replace(new RegExp(String.fromCharCode(174), 'gi'), "(R)"); // ®
html = html.replace(new RegExp(String.fromCharCode(8230), 'gi'), "..."); // …
return FloatHtmlEditor.superclass.cleanHtml.call(this, html);
}
})