extjs3.3 htmleditor各种修正和扩充

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(/^(&nbsp;|<br>|\s)*|(&nbsp;|<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);
	}
})



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值